##// END OF EJS Templates
removal deprecated in 7.10 (#14614)
M Bussonnier -
r29017:602590b8 merge main
parent child Browse files
Show More
@@ -1,1246 +1,1210
1 """Tools for inspecting Python objects.
1 """Tools for inspecting Python objects.
2
2
3 Uses syntax highlighting for presenting the various information elements.
3 Uses syntax highlighting for presenting the various information elements.
4
4
5 Similar in spirit to the inspect module, but all calls take a name argument to
5 Similar in spirit to the inspect module, but all calls take a name argument to
6 reference the name under which an object is being read.
6 reference the name under which an object is being read.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 __all__ = ['Inspector','InspectColors']
12 __all__ = ['Inspector','InspectColors']
13
13
14 # stdlib modules
14 # stdlib modules
15 from dataclasses import dataclass
15 from dataclasses import dataclass
16 from inspect import signature
16 from inspect import signature
17 from textwrap import dedent
17 from textwrap import dedent
18 import ast
18 import ast
19 import html
19 import html
20 import inspect
20 import inspect
21 import io as stdlib_io
21 import io as stdlib_io
22 import linecache
22 import linecache
23 import os
23 import os
24 import types
24 import types
25 import warnings
25 import warnings
26
26
27
27
28 from typing import (
28 from typing import (
29 cast,
29 cast,
30 Any,
30 Any,
31 Optional,
31 Optional,
32 Dict,
32 Dict,
33 Union,
33 Union,
34 List,
34 List,
35 TypedDict,
35 TypedDict,
36 TypeAlias,
36 TypeAlias,
37 Tuple,
37 Tuple,
38 )
38 )
39
39
40 import traitlets
40 import traitlets
41
41
42 # IPython's own
42 # IPython's own
43 from IPython.core import page
43 from IPython.core import page
44 from IPython.lib.pretty import pretty
44 from IPython.lib.pretty import pretty
45 from IPython.testing.skipdoctest import skip_doctest
45 from IPython.testing.skipdoctest import skip_doctest
46 from IPython.utils import PyColorize, openpy
46 from IPython.utils import PyColorize, openpy
47 from IPython.utils.dir2 import safe_hasattr
47 from IPython.utils.dir2 import safe_hasattr
48 from IPython.utils.path import compress_user
48 from IPython.utils.path import compress_user
49 from IPython.utils.text import indent
49 from IPython.utils.text import indent
50 from IPython.utils.wildcard import list_namespace, typestr2type
50 from IPython.utils.wildcard import list_namespace, typestr2type
51 from IPython.utils.coloransi import TermColors
51 from IPython.utils.coloransi import TermColors
52 from IPython.utils.colorable import Colorable
52 from IPython.utils.colorable import Colorable
53 from IPython.utils.decorators import undoc
53 from IPython.utils.decorators import undoc
54
54
55 from pygments import highlight
55 from pygments import highlight
56 from pygments.lexers import PythonLexer
56 from pygments.lexers import PythonLexer
57 from pygments.formatters import HtmlFormatter
57 from pygments.formatters import HtmlFormatter
58
58
59 HOOK_NAME = "__custom_documentations__"
59 HOOK_NAME = "__custom_documentations__"
60
60
61
61
62 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
62 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
63 Bundle: TypeAlias = Dict[str, str]
63 Bundle: TypeAlias = Dict[str, str]
64
64
65
65
66 @dataclass
66 @dataclass
67 class OInfo:
67 class OInfo:
68 ismagic: bool
68 ismagic: bool
69 isalias: bool
69 isalias: bool
70 found: bool
70 found: bool
71 namespace: Optional[str]
71 namespace: Optional[str]
72 parent: Any
72 parent: Any
73 obj: Any
73 obj: Any
74
74
75 def get(self, field):
75 def get(self, field):
76 """Get a field from the object for backward compatibility with before 8.12
76 """Get a field from the object for backward compatibility with before 8.12
77
77
78 see https://github.com/h5py/h5py/issues/2253
78 see https://github.com/h5py/h5py/issues/2253
79 """
79 """
80 # We need to deprecate this at some point, but the warning will show in completion.
80 # We need to deprecate this at some point, but the warning will show in completion.
81 # Let's comment this for now and uncomment end of 2023 ish
81 # Let's comment this for now and uncomment end of 2023 ish
82 # warnings.warn(
82 # warnings.warn(
83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
85 # "This warning and backward compatibility `get()` method were added in 8.13.",
85 # "This warning and backward compatibility `get()` method were added in 8.13.",
86 # DeprecationWarning,
86 # DeprecationWarning,
87 # stacklevel=2,
87 # stacklevel=2,
88 # )
88 # )
89 return getattr(self, field)
89 return getattr(self, field)
90
90
91
91
92 def pylight(code):
92 def pylight(code):
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
94
94
95 # builtin docstrings to ignore
95 # builtin docstrings to ignore
96 _func_call_docstring = types.FunctionType.__call__.__doc__
96 _func_call_docstring = types.FunctionType.__call__.__doc__
97 _object_init_docstring = object.__init__.__doc__
97 _object_init_docstring = object.__init__.__doc__
98 _builtin_type_docstrings = {
98 _builtin_type_docstrings = {
99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
100 types.FunctionType, property)
100 types.FunctionType, property)
101 }
101 }
102
102
103 _builtin_func_type = type(all)
103 _builtin_func_type = type(all)
104 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
104 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
105 #****************************************************************************
105 #****************************************************************************
106 # Builtin color schemes
106 # Builtin color schemes
107
107
108 Colors = TermColors # just a shorthand
108 Colors = TermColors # just a shorthand
109
109
110 InspectColors = PyColorize.ANSICodeColors
110 InspectColors = PyColorize.ANSICodeColors
111
111
112 #****************************************************************************
112 #****************************************************************************
113 # Auxiliary functions and objects
113 # Auxiliary functions and objects
114
114
115
115
116 class InfoDict(TypedDict):
116 class InfoDict(TypedDict):
117 type_name: Optional[str]
117 type_name: Optional[str]
118 base_class: Optional[str]
118 base_class: Optional[str]
119 string_form: Optional[str]
119 string_form: Optional[str]
120 namespace: Optional[str]
120 namespace: Optional[str]
121 length: Optional[str]
121 length: Optional[str]
122 file: Optional[str]
122 file: Optional[str]
123 definition: Optional[str]
123 definition: Optional[str]
124 docstring: Optional[str]
124 docstring: Optional[str]
125 source: Optional[str]
125 source: Optional[str]
126 init_definition: Optional[str]
126 init_definition: Optional[str]
127 class_docstring: Optional[str]
127 class_docstring: Optional[str]
128 init_docstring: Optional[str]
128 init_docstring: Optional[str]
129 call_def: Optional[str]
129 call_def: Optional[str]
130 call_docstring: Optional[str]
130 call_docstring: Optional[str]
131 subclasses: Optional[str]
131 subclasses: Optional[str]
132 # These won't be printed but will be used to determine how to
132 # These won't be printed but will be used to determine how to
133 # format the object
133 # format the object
134 ismagic: bool
134 ismagic: bool
135 isalias: bool
135 isalias: bool
136 isclass: bool
136 isclass: bool
137 found: bool
137 found: bool
138 name: str
138 name: str
139
139
140
140
141 _info_fields = list(InfoDict.__annotations__.keys())
141 _info_fields = list(InfoDict.__annotations__.keys())
142
142
143
143
144 def __getattr__(name):
144 def __getattr__(name):
145 if name == "info_fields":
145 if name == "info_fields":
146 warnings.warn(
146 warnings.warn(
147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ",
147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ",
148 DeprecationWarning,
148 DeprecationWarning,
149 stacklevel=2,
149 stacklevel=2,
150 )
150 )
151 return _info_fields
151 return _info_fields
152
152
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
154
154
155
155
156 @dataclass
156 @dataclass
157 class InspectorHookData:
157 class InspectorHookData:
158 """Data passed to the mime hook"""
158 """Data passed to the mime hook"""
159
159
160 obj: Any
160 obj: Any
161 info: Optional[OInfo]
161 info: Optional[OInfo]
162 info_dict: InfoDict
162 info_dict: InfoDict
163 detail_level: int
163 detail_level: int
164 omit_sections: list[str]
164 omit_sections: list[str]
165
165
166
166
167 @undoc
167 @undoc
168 def object_info(
168 def object_info(
169 *,
169 *,
170 name: str,
170 name: str,
171 found: bool,
171 found: bool,
172 isclass: bool = False,
172 isclass: bool = False,
173 isalias: bool = False,
173 isalias: bool = False,
174 ismagic: bool = False,
174 ismagic: bool = False,
175 **kw,
175 **kw,
176 ) -> InfoDict:
176 ) -> InfoDict:
177 """Make an object info dict with all fields present."""
177 """Make an object info dict with all fields present."""
178 infodict = kw
178 infodict = kw
179 infodict = {k: None for k in _info_fields if k not in infodict}
179 infodict = {k: None for k in _info_fields if k not in infodict}
180 infodict["name"] = name # type: ignore
180 infodict["name"] = name # type: ignore
181 infodict["found"] = found # type: ignore
181 infodict["found"] = found # type: ignore
182 infodict["isclass"] = isclass # type: ignore
182 infodict["isclass"] = isclass # type: ignore
183 infodict["isalias"] = isalias # type: ignore
183 infodict["isalias"] = isalias # type: ignore
184 infodict["ismagic"] = ismagic # type: ignore
184 infodict["ismagic"] = ismagic # type: ignore
185
185
186 return InfoDict(**infodict) # type:ignore
186 return InfoDict(**infodict) # type:ignore
187
187
188
188
189 def get_encoding(obj):
189 def get_encoding(obj):
190 """Get encoding for python source file defining obj
190 """Get encoding for python source file defining obj
191
191
192 Returns None if obj is not defined in a sourcefile.
192 Returns None if obj is not defined in a sourcefile.
193 """
193 """
194 ofile = find_file(obj)
194 ofile = find_file(obj)
195 # run contents of file through pager starting at line where the object
195 # run contents of file through pager starting at line where the object
196 # is defined, as long as the file isn't binary and is actually on the
196 # is defined, as long as the file isn't binary and is actually on the
197 # filesystem.
197 # filesystem.
198 if ofile is None:
198 if ofile is None:
199 return None
199 return None
200 elif ofile.endswith(('.so', '.dll', '.pyd')):
200 elif ofile.endswith(('.so', '.dll', '.pyd')):
201 return None
201 return None
202 elif not os.path.isfile(ofile):
202 elif not os.path.isfile(ofile):
203 return None
203 return None
204 else:
204 else:
205 # Print only text files, not extension binaries. Note that
205 # Print only text files, not extension binaries. Note that
206 # getsourcelines returns lineno with 1-offset and page() uses
206 # getsourcelines returns lineno with 1-offset and page() uses
207 # 0-offset, so we must adjust.
207 # 0-offset, so we must adjust.
208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
209 encoding, _lines = openpy.detect_encoding(buffer.readline)
209 encoding, _lines = openpy.detect_encoding(buffer.readline)
210 return encoding
210 return encoding
211
211
212
212
213 def getdoc(obj) -> Union[str, None]:
213 def getdoc(obj) -> Union[str, None]:
214 """Stable wrapper around inspect.getdoc.
214 """Stable wrapper around inspect.getdoc.
215
215
216 This can't crash because of attribute problems.
216 This can't crash because of attribute problems.
217
217
218 It also attempts to call a getdoc() method on the given object. This
218 It also attempts to call a getdoc() method on the given object. This
219 allows objects which provide their docstrings via non-standard mechanisms
219 allows objects which provide their docstrings via non-standard mechanisms
220 (like Pyro proxies) to still be inspected by ipython's ? system.
220 (like Pyro proxies) to still be inspected by ipython's ? system.
221 """
221 """
222 # Allow objects to offer customized documentation via a getdoc method:
222 # Allow objects to offer customized documentation via a getdoc method:
223 try:
223 try:
224 ds = obj.getdoc()
224 ds = obj.getdoc()
225 except Exception:
225 except Exception:
226 pass
226 pass
227 else:
227 else:
228 if isinstance(ds, str):
228 if isinstance(ds, str):
229 return inspect.cleandoc(ds)
229 return inspect.cleandoc(ds)
230 docstr = inspect.getdoc(obj)
230 docstr = inspect.getdoc(obj)
231 return docstr
231 return docstr
232
232
233
233
234 def getsource(obj, oname='') -> Union[str,None]:
234 def getsource(obj, oname='') -> Union[str,None]:
235 """Wrapper around inspect.getsource.
235 """Wrapper around inspect.getsource.
236
236
237 This can be modified by other projects to provide customized source
237 This can be modified by other projects to provide customized source
238 extraction.
238 extraction.
239
239
240 Parameters
240 Parameters
241 ----------
241 ----------
242 obj : object
242 obj : object
243 an object whose source code we will attempt to extract
243 an object whose source code we will attempt to extract
244 oname : str
244 oname : str
245 (optional) a name under which the object is known
245 (optional) a name under which the object is known
246
246
247 Returns
247 Returns
248 -------
248 -------
249 src : unicode or None
249 src : unicode or None
250
250
251 """
251 """
252
252
253 if isinstance(obj, property):
253 if isinstance(obj, property):
254 sources = []
254 sources = []
255 for attrname in ['fget', 'fset', 'fdel']:
255 for attrname in ['fget', 'fset', 'fdel']:
256 fn = getattr(obj, attrname)
256 fn = getattr(obj, attrname)
257 if fn is not None:
257 if fn is not None:
258 encoding = get_encoding(fn)
258 encoding = get_encoding(fn)
259 oname_prefix = ('%s.' % oname) if oname else ''
259 oname_prefix = ('%s.' % oname) if oname else ''
260 sources.append(''.join(('# ', oname_prefix, attrname)))
260 sources.append(''.join(('# ', oname_prefix, attrname)))
261 if inspect.isfunction(fn):
261 if inspect.isfunction(fn):
262 _src = getsource(fn)
262 _src = getsource(fn)
263 if _src:
263 if _src:
264 # assert _src is not None, "please mypy"
264 # assert _src is not None, "please mypy"
265 sources.append(dedent(_src))
265 sources.append(dedent(_src))
266 else:
266 else:
267 # Default str/repr only prints function name,
267 # Default str/repr only prints function name,
268 # pretty.pretty prints module name too.
268 # pretty.pretty prints module name too.
269 sources.append(
269 sources.append(
270 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
270 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
271 )
271 )
272 if sources:
272 if sources:
273 return '\n'.join(sources)
273 return '\n'.join(sources)
274 else:
274 else:
275 return None
275 return None
276
276
277 else:
277 else:
278 # Get source for non-property objects.
278 # Get source for non-property objects.
279
279
280 obj = _get_wrapped(obj)
280 obj = _get_wrapped(obj)
281
281
282 try:
282 try:
283 src = inspect.getsource(obj)
283 src = inspect.getsource(obj)
284 except TypeError:
284 except TypeError:
285 # The object itself provided no meaningful source, try looking for
285 # The object itself provided no meaningful source, try looking for
286 # its class definition instead.
286 # its class definition instead.
287 try:
287 try:
288 src = inspect.getsource(obj.__class__)
288 src = inspect.getsource(obj.__class__)
289 except (OSError, TypeError):
289 except (OSError, TypeError):
290 return None
290 return None
291 except OSError:
291 except OSError:
292 return None
292 return None
293
293
294 return src
294 return src
295
295
296
296
297 def is_simple_callable(obj):
297 def is_simple_callable(obj):
298 """True if obj is a function ()"""
298 """True if obj is a function ()"""
299 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
299 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
300 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
300 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
301
301
302 @undoc
303 def getargspec(obj):
304 """Wrapper around :func:`inspect.getfullargspec`
305
306 In addition to functions and methods, this can also handle objects with a
307 ``__call__`` attribute.
308
309 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
310 """
311
312 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
313 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
314
315 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
316 obj = obj.__call__
317
318 return inspect.getfullargspec(obj)
319
320 @undoc
321 def format_argspec(argspec):
322 """Format argspect, convenience wrapper around inspect's.
323
324 This takes a dict instead of ordered arguments and calls
325 inspect.format_argspec with the arguments in the necessary order.
326
327 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
328 """
329
330 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
331 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
332
333
334 return inspect.formatargspec(argspec['args'], argspec['varargs'],
335 argspec['varkw'], argspec['defaults'])
336
337
338 def _get_wrapped(obj):
302 def _get_wrapped(obj):
339 """Get the original object if wrapped in one or more @decorators
303 """Get the original object if wrapped in one or more @decorators
340
304
341 Some objects automatically construct similar objects on any unrecognised
305 Some objects automatically construct similar objects on any unrecognised
342 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
306 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
343 this will arbitrarily cut off after 100 levels of obj.__wrapped__
307 this will arbitrarily cut off after 100 levels of obj.__wrapped__
344 attribute access. --TK, Jan 2016
308 attribute access. --TK, Jan 2016
345 """
309 """
346 orig_obj = obj
310 orig_obj = obj
347 i = 0
311 i = 0
348 while safe_hasattr(obj, '__wrapped__'):
312 while safe_hasattr(obj, '__wrapped__'):
349 obj = obj.__wrapped__
313 obj = obj.__wrapped__
350 i += 1
314 i += 1
351 if i > 100:
315 if i > 100:
352 # __wrapped__ is probably a lie, so return the thing we started with
316 # __wrapped__ is probably a lie, so return the thing we started with
353 return orig_obj
317 return orig_obj
354 return obj
318 return obj
355
319
356 def find_file(obj) -> Optional[str]:
320 def find_file(obj) -> Optional[str]:
357 """Find the absolute path to the file where an object was defined.
321 """Find the absolute path to the file where an object was defined.
358
322
359 This is essentially a robust wrapper around `inspect.getabsfile`.
323 This is essentially a robust wrapper around `inspect.getabsfile`.
360
324
361 Returns None if no file can be found.
325 Returns None if no file can be found.
362
326
363 Parameters
327 Parameters
364 ----------
328 ----------
365 obj : any Python object
329 obj : any Python object
366
330
367 Returns
331 Returns
368 -------
332 -------
369 fname : str
333 fname : str
370 The absolute path to the file where the object was defined.
334 The absolute path to the file where the object was defined.
371 """
335 """
372 obj = _get_wrapped(obj)
336 obj = _get_wrapped(obj)
373
337
374 fname: Optional[str] = None
338 fname: Optional[str] = None
375 try:
339 try:
376 fname = inspect.getabsfile(obj)
340 fname = inspect.getabsfile(obj)
377 except TypeError:
341 except TypeError:
378 # For an instance, the file that matters is where its class was
342 # For an instance, the file that matters is where its class was
379 # declared.
343 # declared.
380 try:
344 try:
381 fname = inspect.getabsfile(obj.__class__)
345 fname = inspect.getabsfile(obj.__class__)
382 except (OSError, TypeError):
346 except (OSError, TypeError):
383 # Can happen for builtins
347 # Can happen for builtins
384 pass
348 pass
385 except OSError:
349 except OSError:
386 pass
350 pass
387
351
388 return fname
352 return fname
389
353
390
354
391 def find_source_lines(obj):
355 def find_source_lines(obj):
392 """Find the line number in a file where an object was defined.
356 """Find the line number in a file where an object was defined.
393
357
394 This is essentially a robust wrapper around `inspect.getsourcelines`.
358 This is essentially a robust wrapper around `inspect.getsourcelines`.
395
359
396 Returns None if no file can be found.
360 Returns None if no file can be found.
397
361
398 Parameters
362 Parameters
399 ----------
363 ----------
400 obj : any Python object
364 obj : any Python object
401
365
402 Returns
366 Returns
403 -------
367 -------
404 lineno : int
368 lineno : int
405 The line number where the object definition starts.
369 The line number where the object definition starts.
406 """
370 """
407 obj = _get_wrapped(obj)
371 obj = _get_wrapped(obj)
408
372
409 try:
373 try:
410 lineno = inspect.getsourcelines(obj)[1]
374 lineno = inspect.getsourcelines(obj)[1]
411 except TypeError:
375 except TypeError:
412 # For instances, try the class object like getsource() does
376 # For instances, try the class object like getsource() does
413 try:
377 try:
414 lineno = inspect.getsourcelines(obj.__class__)[1]
378 lineno = inspect.getsourcelines(obj.__class__)[1]
415 except (OSError, TypeError):
379 except (OSError, TypeError):
416 return None
380 return None
417 except OSError:
381 except OSError:
418 return None
382 return None
419
383
420 return lineno
384 return lineno
421
385
422 class Inspector(Colorable):
386 class Inspector(Colorable):
423
387
424 mime_hooks = traitlets.Dict(
388 mime_hooks = traitlets.Dict(
425 config=True,
389 config=True,
426 help="dictionary of mime to callable to add information into help mimebundle dict",
390 help="dictionary of mime to callable to add information into help mimebundle dict",
427 ).tag(config=True)
391 ).tag(config=True)
428
392
429 def __init__(
393 def __init__(
430 self,
394 self,
431 color_table=InspectColors,
395 color_table=InspectColors,
432 code_color_table=PyColorize.ANSICodeColors,
396 code_color_table=PyColorize.ANSICodeColors,
433 scheme=None,
397 scheme=None,
434 str_detail_level=0,
398 str_detail_level=0,
435 parent=None,
399 parent=None,
436 config=None,
400 config=None,
437 ):
401 ):
438 super(Inspector, self).__init__(parent=parent, config=config)
402 super(Inspector, self).__init__(parent=parent, config=config)
439 self.color_table = color_table
403 self.color_table = color_table
440 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
404 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
441 self.format = self.parser.format
405 self.format = self.parser.format
442 self.str_detail_level = str_detail_level
406 self.str_detail_level = str_detail_level
443 self.set_active_scheme(scheme)
407 self.set_active_scheme(scheme)
444
408
445 def _getdef(self,obj,oname='') -> Union[str,None]:
409 def _getdef(self,obj,oname='') -> Union[str,None]:
446 """Return the call signature for any callable object.
410 """Return the call signature for any callable object.
447
411
448 If any exception is generated, None is returned instead and the
412 If any exception is generated, None is returned instead and the
449 exception is suppressed."""
413 exception is suppressed."""
450 if not callable(obj):
414 if not callable(obj):
451 return None
415 return None
452 try:
416 try:
453 return _render_signature(signature(obj), oname)
417 return _render_signature(signature(obj), oname)
454 except:
418 except:
455 return None
419 return None
456
420
457 def __head(self,h) -> str:
421 def __head(self,h) -> str:
458 """Return a header string with proper colors."""
422 """Return a header string with proper colors."""
459 return '%s%s%s' % (self.color_table.active_colors.header,h,
423 return '%s%s%s' % (self.color_table.active_colors.header,h,
460 self.color_table.active_colors.normal)
424 self.color_table.active_colors.normal)
461
425
462 def set_active_scheme(self, scheme):
426 def set_active_scheme(self, scheme):
463 if scheme is not None:
427 if scheme is not None:
464 self.color_table.set_active_scheme(scheme)
428 self.color_table.set_active_scheme(scheme)
465 self.parser.color_table.set_active_scheme(scheme)
429 self.parser.color_table.set_active_scheme(scheme)
466
430
467 def noinfo(self, msg, oname):
431 def noinfo(self, msg, oname):
468 """Generic message when no information is found."""
432 """Generic message when no information is found."""
469 print('No %s found' % msg, end=' ')
433 print('No %s found' % msg, end=' ')
470 if oname:
434 if oname:
471 print('for %s' % oname)
435 print('for %s' % oname)
472 else:
436 else:
473 print()
437 print()
474
438
475 def pdef(self, obj, oname=''):
439 def pdef(self, obj, oname=''):
476 """Print the call signature for any callable object.
440 """Print the call signature for any callable object.
477
441
478 If the object is a class, print the constructor information."""
442 If the object is a class, print the constructor information."""
479
443
480 if not callable(obj):
444 if not callable(obj):
481 print('Object is not callable.')
445 print('Object is not callable.')
482 return
446 return
483
447
484 header = ''
448 header = ''
485
449
486 if inspect.isclass(obj):
450 if inspect.isclass(obj):
487 header = self.__head('Class constructor information:\n')
451 header = self.__head('Class constructor information:\n')
488
452
489
453
490 output = self._getdef(obj,oname)
454 output = self._getdef(obj,oname)
491 if output is None:
455 if output is None:
492 self.noinfo('definition header',oname)
456 self.noinfo('definition header',oname)
493 else:
457 else:
494 print(header,self.format(output), end=' ')
458 print(header,self.format(output), end=' ')
495
459
496 # In Python 3, all classes are new-style, so they all have __init__.
460 # In Python 3, all classes are new-style, so they all have __init__.
497 @skip_doctest
461 @skip_doctest
498 def pdoc(self, obj, oname='', formatter=None):
462 def pdoc(self, obj, oname='', formatter=None):
499 """Print the docstring for any object.
463 """Print the docstring for any object.
500
464
501 Optional:
465 Optional:
502 -formatter: a function to run the docstring through for specially
466 -formatter: a function to run the docstring through for specially
503 formatted docstrings.
467 formatted docstrings.
504
468
505 Examples
469 Examples
506 --------
470 --------
507 In [1]: class NoInit:
471 In [1]: class NoInit:
508 ...: pass
472 ...: pass
509
473
510 In [2]: class NoDoc:
474 In [2]: class NoDoc:
511 ...: def __init__(self):
475 ...: def __init__(self):
512 ...: pass
476 ...: pass
513
477
514 In [3]: %pdoc NoDoc
478 In [3]: %pdoc NoDoc
515 No documentation found for NoDoc
479 No documentation found for NoDoc
516
480
517 In [4]: %pdoc NoInit
481 In [4]: %pdoc NoInit
518 No documentation found for NoInit
482 No documentation found for NoInit
519
483
520 In [5]: obj = NoInit()
484 In [5]: obj = NoInit()
521
485
522 In [6]: %pdoc obj
486 In [6]: %pdoc obj
523 No documentation found for obj
487 No documentation found for obj
524
488
525 In [5]: obj2 = NoDoc()
489 In [5]: obj2 = NoDoc()
526
490
527 In [6]: %pdoc obj2
491 In [6]: %pdoc obj2
528 No documentation found for obj2
492 No documentation found for obj2
529 """
493 """
530
494
531 head = self.__head # For convenience
495 head = self.__head # For convenience
532 lines = []
496 lines = []
533 ds = getdoc(obj)
497 ds = getdoc(obj)
534 if formatter:
498 if formatter:
535 ds = formatter(ds).get('plain/text', ds)
499 ds = formatter(ds).get('plain/text', ds)
536 if ds:
500 if ds:
537 lines.append(head("Class docstring:"))
501 lines.append(head("Class docstring:"))
538 lines.append(indent(ds))
502 lines.append(indent(ds))
539 if inspect.isclass(obj) and hasattr(obj, '__init__'):
503 if inspect.isclass(obj) and hasattr(obj, '__init__'):
540 init_ds = getdoc(obj.__init__)
504 init_ds = getdoc(obj.__init__)
541 if init_ds is not None:
505 if init_ds is not None:
542 lines.append(head("Init docstring:"))
506 lines.append(head("Init docstring:"))
543 lines.append(indent(init_ds))
507 lines.append(indent(init_ds))
544 elif hasattr(obj,'__call__'):
508 elif hasattr(obj,'__call__'):
545 call_ds = getdoc(obj.__call__)
509 call_ds = getdoc(obj.__call__)
546 if call_ds:
510 if call_ds:
547 lines.append(head("Call docstring:"))
511 lines.append(head("Call docstring:"))
548 lines.append(indent(call_ds))
512 lines.append(indent(call_ds))
549
513
550 if not lines:
514 if not lines:
551 self.noinfo('documentation',oname)
515 self.noinfo('documentation',oname)
552 else:
516 else:
553 page.page('\n'.join(lines))
517 page.page('\n'.join(lines))
554
518
555 def psource(self, obj, oname=''):
519 def psource(self, obj, oname=''):
556 """Print the source code for an object."""
520 """Print the source code for an object."""
557
521
558 # Flush the source cache because inspect can return out-of-date source
522 # Flush the source cache because inspect can return out-of-date source
559 linecache.checkcache()
523 linecache.checkcache()
560 try:
524 try:
561 src = getsource(obj, oname=oname)
525 src = getsource(obj, oname=oname)
562 except Exception:
526 except Exception:
563 src = None
527 src = None
564
528
565 if src is None:
529 if src is None:
566 self.noinfo('source', oname)
530 self.noinfo('source', oname)
567 else:
531 else:
568 page.page(self.format(src))
532 page.page(self.format(src))
569
533
570 def pfile(self, obj, oname=''):
534 def pfile(self, obj, oname=''):
571 """Show the whole file where an object was defined."""
535 """Show the whole file where an object was defined."""
572
536
573 lineno = find_source_lines(obj)
537 lineno = find_source_lines(obj)
574 if lineno is None:
538 if lineno is None:
575 self.noinfo('file', oname)
539 self.noinfo('file', oname)
576 return
540 return
577
541
578 ofile = find_file(obj)
542 ofile = find_file(obj)
579 # run contents of file through pager starting at line where the object
543 # run contents of file through pager starting at line where the object
580 # is defined, as long as the file isn't binary and is actually on the
544 # is defined, as long as the file isn't binary and is actually on the
581 # filesystem.
545 # filesystem.
582 if ofile is None:
546 if ofile is None:
583 print("Could not find file for object")
547 print("Could not find file for object")
584 elif ofile.endswith((".so", ".dll", ".pyd")):
548 elif ofile.endswith((".so", ".dll", ".pyd")):
585 print("File %r is binary, not printing." % ofile)
549 print("File %r is binary, not printing." % ofile)
586 elif not os.path.isfile(ofile):
550 elif not os.path.isfile(ofile):
587 print('File %r does not exist, not printing.' % ofile)
551 print('File %r does not exist, not printing.' % ofile)
588 else:
552 else:
589 # Print only text files, not extension binaries. Note that
553 # Print only text files, not extension binaries. Note that
590 # getsourcelines returns lineno with 1-offset and page() uses
554 # getsourcelines returns lineno with 1-offset and page() uses
591 # 0-offset, so we must adjust.
555 # 0-offset, so we must adjust.
592 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
556 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
593
557
594
558
595 def _mime_format(self, text:str, formatter=None) -> dict:
559 def _mime_format(self, text:str, formatter=None) -> dict:
596 """Return a mime bundle representation of the input text.
560 """Return a mime bundle representation of the input text.
597
561
598 - if `formatter` is None, the returned mime bundle has
562 - if `formatter` is None, the returned mime bundle has
599 a ``text/plain`` field, with the input text.
563 a ``text/plain`` field, with the input text.
600 a ``text/html`` field with a ``<pre>`` tag containing the input text.
564 a ``text/html`` field with a ``<pre>`` tag containing the input text.
601
565
602 - if ``formatter`` is not None, it must be a callable transforming the
566 - if ``formatter`` is not None, it must be a callable transforming the
603 input text into a mime bundle. Default values for ``text/plain`` and
567 input text into a mime bundle. Default values for ``text/plain`` and
604 ``text/html`` representations are the ones described above.
568 ``text/html`` representations are the ones described above.
605
569
606 Note:
570 Note:
607
571
608 Formatters returning strings are supported but this behavior is deprecated.
572 Formatters returning strings are supported but this behavior is deprecated.
609
573
610 """
574 """
611 defaults = {
575 defaults = {
612 "text/plain": text,
576 "text/plain": text,
613 "text/html": f"<pre>{html.escape(text)}</pre>",
577 "text/html": f"<pre>{html.escape(text)}</pre>",
614 }
578 }
615
579
616 if formatter is None:
580 if formatter is None:
617 return defaults
581 return defaults
618 else:
582 else:
619 formatted = formatter(text)
583 formatted = formatter(text)
620
584
621 if not isinstance(formatted, dict):
585 if not isinstance(formatted, dict):
622 # Handle the deprecated behavior of a formatter returning
586 # Handle the deprecated behavior of a formatter returning
623 # a string instead of a mime bundle.
587 # a string instead of a mime bundle.
624 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
588 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
625
589
626 else:
590 else:
627 return dict(defaults, **formatted)
591 return dict(defaults, **formatted)
628
592
629 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
593 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
630 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
594 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
631 # Format text/plain mimetype
595 # Format text/plain mimetype
632 assert isinstance(bundle["text/plain"], list)
596 assert isinstance(bundle["text/plain"], list)
633 for item in bundle["text/plain"]:
597 for item in bundle["text/plain"]:
634 assert isinstance(item, tuple)
598 assert isinstance(item, tuple)
635
599
636 new_b: Bundle = {}
600 new_b: Bundle = {}
637 lines = []
601 lines = []
638 _len = max(len(h) for h, _ in bundle["text/plain"])
602 _len = max(len(h) for h, _ in bundle["text/plain"])
639
603
640 for head, body in bundle["text/plain"]:
604 for head, body in bundle["text/plain"]:
641 body = body.strip("\n")
605 body = body.strip("\n")
642 delim = "\n" if "\n" in body else " "
606 delim = "\n" if "\n" in body else " "
643 lines.append(
607 lines.append(
644 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
608 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
645 )
609 )
646
610
647 new_b["text/plain"] = "\n".join(lines)
611 new_b["text/plain"] = "\n".join(lines)
648
612
649 if "text/html" in bundle:
613 if "text/html" in bundle:
650 assert isinstance(bundle["text/html"], list)
614 assert isinstance(bundle["text/html"], list)
651 for item in bundle["text/html"]:
615 for item in bundle["text/html"]:
652 assert isinstance(item, tuple)
616 assert isinstance(item, tuple)
653 # Format the text/html mimetype
617 # Format the text/html mimetype
654 if isinstance(bundle["text/html"], (list, tuple)):
618 if isinstance(bundle["text/html"], (list, tuple)):
655 # bundle['text/html'] is a list of (head, formatted body) pairs
619 # bundle['text/html'] is a list of (head, formatted body) pairs
656 new_b["text/html"] = "\n".join(
620 new_b["text/html"] = "\n".join(
657 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
621 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
658 )
622 )
659
623
660 for k in bundle.keys():
624 for k in bundle.keys():
661 if k in ("text/html", "text/plain"):
625 if k in ("text/html", "text/plain"):
662 continue
626 continue
663 else:
627 else:
664 new_b[k] = bundle[k] # type:ignore
628 new_b[k] = bundle[k] # type:ignore
665 return new_b
629 return new_b
666
630
667 def _append_info_field(
631 def _append_info_field(
668 self,
632 self,
669 bundle: UnformattedBundle,
633 bundle: UnformattedBundle,
670 title: str,
634 title: str,
671 key: str,
635 key: str,
672 info,
636 info,
673 omit_sections: List[str],
637 omit_sections: List[str],
674 formatter,
638 formatter,
675 ):
639 ):
676 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
640 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
677 if title in omit_sections or key in omit_sections:
641 if title in omit_sections or key in omit_sections:
678 return
642 return
679 field = info[key]
643 field = info[key]
680 if field is not None:
644 if field is not None:
681 formatted_field = self._mime_format(field, formatter)
645 formatted_field = self._mime_format(field, formatter)
682 bundle["text/plain"].append((title, formatted_field["text/plain"]))
646 bundle["text/plain"].append((title, formatted_field["text/plain"]))
683 bundle["text/html"].append((title, formatted_field["text/html"]))
647 bundle["text/html"].append((title, formatted_field["text/html"]))
684
648
685 def _make_info_unformatted(
649 def _make_info_unformatted(
686 self, obj, info, formatter, detail_level, omit_sections
650 self, obj, info, formatter, detail_level, omit_sections
687 ) -> UnformattedBundle:
651 ) -> UnformattedBundle:
688 """Assemble the mimebundle as unformatted lists of information"""
652 """Assemble the mimebundle as unformatted lists of information"""
689 bundle: UnformattedBundle = {
653 bundle: UnformattedBundle = {
690 "text/plain": [],
654 "text/plain": [],
691 "text/html": [],
655 "text/html": [],
692 }
656 }
693
657
694 # A convenience function to simplify calls below
658 # A convenience function to simplify calls below
695 def append_field(
659 def append_field(
696 bundle: UnformattedBundle, title: str, key: str, formatter=None
660 bundle: UnformattedBundle, title: str, key: str, formatter=None
697 ):
661 ):
698 self._append_info_field(
662 self._append_info_field(
699 bundle,
663 bundle,
700 title=title,
664 title=title,
701 key=key,
665 key=key,
702 info=info,
666 info=info,
703 omit_sections=omit_sections,
667 omit_sections=omit_sections,
704 formatter=formatter,
668 formatter=formatter,
705 )
669 )
706
670
707 def code_formatter(text) -> Bundle:
671 def code_formatter(text) -> Bundle:
708 return {
672 return {
709 'text/plain': self.format(text),
673 'text/plain': self.format(text),
710 'text/html': pylight(text)
674 'text/html': pylight(text)
711 }
675 }
712
676
713 if info["isalias"]:
677 if info["isalias"]:
714 append_field(bundle, "Repr", "string_form")
678 append_field(bundle, "Repr", "string_form")
715
679
716 elif info['ismagic']:
680 elif info['ismagic']:
717 if detail_level > 0:
681 if detail_level > 0:
718 append_field(bundle, "Source", "source", code_formatter)
682 append_field(bundle, "Source", "source", code_formatter)
719 else:
683 else:
720 append_field(bundle, "Docstring", "docstring", formatter)
684 append_field(bundle, "Docstring", "docstring", formatter)
721 append_field(bundle, "File", "file")
685 append_field(bundle, "File", "file")
722
686
723 elif info['isclass'] or is_simple_callable(obj):
687 elif info['isclass'] or is_simple_callable(obj):
724 # Functions, methods, classes
688 # Functions, methods, classes
725 append_field(bundle, "Signature", "definition", code_formatter)
689 append_field(bundle, "Signature", "definition", code_formatter)
726 append_field(bundle, "Init signature", "init_definition", code_formatter)
690 append_field(bundle, "Init signature", "init_definition", code_formatter)
727 append_field(bundle, "Docstring", "docstring", formatter)
691 append_field(bundle, "Docstring", "docstring", formatter)
728 if detail_level > 0 and info["source"]:
692 if detail_level > 0 and info["source"]:
729 append_field(bundle, "Source", "source", code_formatter)
693 append_field(bundle, "Source", "source", code_formatter)
730 else:
694 else:
731 append_field(bundle, "Init docstring", "init_docstring", formatter)
695 append_field(bundle, "Init docstring", "init_docstring", formatter)
732
696
733 append_field(bundle, "File", "file")
697 append_field(bundle, "File", "file")
734 append_field(bundle, "Type", "type_name")
698 append_field(bundle, "Type", "type_name")
735 append_field(bundle, "Subclasses", "subclasses")
699 append_field(bundle, "Subclasses", "subclasses")
736
700
737 else:
701 else:
738 # General Python objects
702 # General Python objects
739 append_field(bundle, "Signature", "definition", code_formatter)
703 append_field(bundle, "Signature", "definition", code_formatter)
740 append_field(bundle, "Call signature", "call_def", code_formatter)
704 append_field(bundle, "Call signature", "call_def", code_formatter)
741 append_field(bundle, "Type", "type_name")
705 append_field(bundle, "Type", "type_name")
742 append_field(bundle, "String form", "string_form")
706 append_field(bundle, "String form", "string_form")
743
707
744 # Namespace
708 # Namespace
745 if info["namespace"] != "Interactive":
709 if info["namespace"] != "Interactive":
746 append_field(bundle, "Namespace", "namespace")
710 append_field(bundle, "Namespace", "namespace")
747
711
748 append_field(bundle, "Length", "length")
712 append_field(bundle, "Length", "length")
749 append_field(bundle, "File", "file")
713 append_field(bundle, "File", "file")
750
714
751 # Source or docstring, depending on detail level and whether
715 # Source or docstring, depending on detail level and whether
752 # source found.
716 # source found.
753 if detail_level > 0 and info["source"]:
717 if detail_level > 0 and info["source"]:
754 append_field(bundle, "Source", "source", code_formatter)
718 append_field(bundle, "Source", "source", code_formatter)
755 else:
719 else:
756 append_field(bundle, "Docstring", "docstring", formatter)
720 append_field(bundle, "Docstring", "docstring", formatter)
757
721
758 append_field(bundle, "Class docstring", "class_docstring", formatter)
722 append_field(bundle, "Class docstring", "class_docstring", formatter)
759 append_field(bundle, "Init docstring", "init_docstring", formatter)
723 append_field(bundle, "Init docstring", "init_docstring", formatter)
760 append_field(bundle, "Call docstring", "call_docstring", formatter)
724 append_field(bundle, "Call docstring", "call_docstring", formatter)
761 return bundle
725 return bundle
762
726
763
727
764 def _get_info(
728 def _get_info(
765 self,
729 self,
766 obj: Any,
730 obj: Any,
767 oname: str = "",
731 oname: str = "",
768 formatter=None,
732 formatter=None,
769 info: Optional[OInfo] = None,
733 info: Optional[OInfo] = None,
770 detail_level: int = 0,
734 detail_level: int = 0,
771 omit_sections: Union[List[str], Tuple[()]] = (),
735 omit_sections: Union[List[str], Tuple[()]] = (),
772 ) -> Bundle:
736 ) -> Bundle:
773 """Retrieve an info dict and format it.
737 """Retrieve an info dict and format it.
774
738
775 Parameters
739 Parameters
776 ----------
740 ----------
777 obj : any
741 obj : any
778 Object to inspect and return info from
742 Object to inspect and return info from
779 oname : str (default: ''):
743 oname : str (default: ''):
780 Name of the variable pointing to `obj`.
744 Name of the variable pointing to `obj`.
781 formatter : callable
745 formatter : callable
782 info
746 info
783 already computed information
747 already computed information
784 detail_level : integer
748 detail_level : integer
785 Granularity of detail level, if set to 1, give more information.
749 Granularity of detail level, if set to 1, give more information.
786 omit_sections : list[str]
750 omit_sections : list[str]
787 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
751 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
788 """
752 """
789
753
790 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
754 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
791 omit_sections = list(omit_sections)
755 omit_sections = list(omit_sections)
792
756
793 bundle = self._make_info_unformatted(
757 bundle = self._make_info_unformatted(
794 obj,
758 obj,
795 info_dict,
759 info_dict,
796 formatter,
760 formatter,
797 detail_level=detail_level,
761 detail_level=detail_level,
798 omit_sections=omit_sections,
762 omit_sections=omit_sections,
799 )
763 )
800 if self.mime_hooks:
764 if self.mime_hooks:
801 hook_data = InspectorHookData(
765 hook_data = InspectorHookData(
802 obj=obj,
766 obj=obj,
803 info=info,
767 info=info,
804 info_dict=info_dict,
768 info_dict=info_dict,
805 detail_level=detail_level,
769 detail_level=detail_level,
806 omit_sections=omit_sections,
770 omit_sections=omit_sections,
807 )
771 )
808 for key, hook in self.mime_hooks.items(): # type:ignore
772 for key, hook in self.mime_hooks.items(): # type:ignore
809 required_parameters = [
773 required_parameters = [
810 parameter
774 parameter
811 for parameter in inspect.signature(hook).parameters.values()
775 for parameter in inspect.signature(hook).parameters.values()
812 if parameter.default != inspect.Parameter.default
776 if parameter.default != inspect.Parameter.default
813 ]
777 ]
814 if len(required_parameters) == 1:
778 if len(required_parameters) == 1:
815 res = hook(hook_data)
779 res = hook(hook_data)
816 else:
780 else:
817 warnings.warn(
781 warnings.warn(
818 "MIME hook format changed in IPython 8.22; hooks should now accept"
782 "MIME hook format changed in IPython 8.22; hooks should now accept"
819 " a single parameter (InspectorHookData); support for hooks requiring"
783 " a single parameter (InspectorHookData); support for hooks requiring"
820 " two-parameters (obj and info) will be removed in a future version",
784 " two-parameters (obj and info) will be removed in a future version",
821 DeprecationWarning,
785 DeprecationWarning,
822 stacklevel=2,
786 stacklevel=2,
823 )
787 )
824 res = hook(obj, info)
788 res = hook(obj, info)
825 if res is not None:
789 if res is not None:
826 bundle[key] = res
790 bundle[key] = res
827 return self.format_mime(bundle)
791 return self.format_mime(bundle)
828
792
829 def pinfo(
793 def pinfo(
830 self,
794 self,
831 obj,
795 obj,
832 oname="",
796 oname="",
833 formatter=None,
797 formatter=None,
834 info: Optional[OInfo] = None,
798 info: Optional[OInfo] = None,
835 detail_level=0,
799 detail_level=0,
836 enable_html_pager=True,
800 enable_html_pager=True,
837 omit_sections=(),
801 omit_sections=(),
838 ):
802 ):
839 """Show detailed information about an object.
803 """Show detailed information about an object.
840
804
841 Optional arguments:
805 Optional arguments:
842
806
843 - oname: name of the variable pointing to the object.
807 - oname: name of the variable pointing to the object.
844
808
845 - formatter: callable (optional)
809 - formatter: callable (optional)
846 A special formatter for docstrings.
810 A special formatter for docstrings.
847
811
848 The formatter is a callable that takes a string as an input
812 The formatter is a callable that takes a string as an input
849 and returns either a formatted string or a mime type bundle
813 and returns either a formatted string or a mime type bundle
850 in the form of a dictionary.
814 in the form of a dictionary.
851
815
852 Although the support of custom formatter returning a string
816 Although the support of custom formatter returning a string
853 instead of a mime type bundle is deprecated.
817 instead of a mime type bundle is deprecated.
854
818
855 - info: a structure with some information fields which may have been
819 - info: a structure with some information fields which may have been
856 precomputed already.
820 precomputed already.
857
821
858 - detail_level: if set to 1, more information is given.
822 - detail_level: if set to 1, more information is given.
859
823
860 - omit_sections: set of section keys and titles to omit
824 - omit_sections: set of section keys and titles to omit
861 """
825 """
862 assert info is not None
826 assert info is not None
863 info_b: Bundle = self._get_info(
827 info_b: Bundle = self._get_info(
864 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
828 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
865 )
829 )
866 if not enable_html_pager:
830 if not enable_html_pager:
867 del info_b["text/html"]
831 del info_b["text/html"]
868 page.page(info_b)
832 page.page(info_b)
869
833
870 def _info(self, obj, oname="", info=None, detail_level=0):
834 def _info(self, obj, oname="", info=None, detail_level=0):
871 """
835 """
872 Inspector.info() was likely improperly marked as deprecated
836 Inspector.info() was likely improperly marked as deprecated
873 while only a parameter was deprecated. We "un-deprecate" it.
837 while only a parameter was deprecated. We "un-deprecate" it.
874 """
838 """
875
839
876 warnings.warn(
840 warnings.warn(
877 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
841 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
878 "and the `formatter=` keyword removed. `Inspector._info` is now "
842 "and the `formatter=` keyword removed. `Inspector._info` is now "
879 "an alias, and you can just call `.info()` directly.",
843 "an alias, and you can just call `.info()` directly.",
880 DeprecationWarning,
844 DeprecationWarning,
881 stacklevel=2,
845 stacklevel=2,
882 )
846 )
883 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
847 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
884
848
885 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
849 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
886 """Compute a dict with detailed information about an object.
850 """Compute a dict with detailed information about an object.
887
851
888 Parameters
852 Parameters
889 ----------
853 ----------
890 obj : any
854 obj : any
891 An object to find information about
855 An object to find information about
892 oname : str (default: '')
856 oname : str (default: '')
893 Name of the variable pointing to `obj`.
857 Name of the variable pointing to `obj`.
894 info : (default: None)
858 info : (default: None)
895 A struct (dict like with attr access) with some information fields
859 A struct (dict like with attr access) with some information fields
896 which may have been precomputed already.
860 which may have been precomputed already.
897 detail_level : int (default:0)
861 detail_level : int (default:0)
898 If set to 1, more information is given.
862 If set to 1, more information is given.
899
863
900 Returns
864 Returns
901 -------
865 -------
902 An object info dict with known fields from `info_fields` (see `InfoDict`).
866 An object info dict with known fields from `info_fields` (see `InfoDict`).
903 """
867 """
904
868
905 if info is None:
869 if info is None:
906 ismagic = False
870 ismagic = False
907 isalias = False
871 isalias = False
908 ospace = ''
872 ospace = ''
909 else:
873 else:
910 ismagic = info.ismagic
874 ismagic = info.ismagic
911 isalias = info.isalias
875 isalias = info.isalias
912 ospace = info.namespace
876 ospace = info.namespace
913
877
914 # Get docstring, special-casing aliases:
878 # Get docstring, special-casing aliases:
915 att_name = oname.split(".")[-1]
879 att_name = oname.split(".")[-1]
916 parents_docs = None
880 parents_docs = None
917 prelude = ""
881 prelude = ""
918 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
882 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
919 parents_docs_dict = getattr(info.parent, HOOK_NAME)
883 parents_docs_dict = getattr(info.parent, HOOK_NAME)
920 parents_docs = parents_docs_dict.get(att_name, None)
884 parents_docs = parents_docs_dict.get(att_name, None)
921 out: InfoDict = cast(
885 out: InfoDict = cast(
922 InfoDict,
886 InfoDict,
923 {
887 {
924 **{field: None for field in _info_fields},
888 **{field: None for field in _info_fields},
925 **{
889 **{
926 "name": oname,
890 "name": oname,
927 "found": True,
891 "found": True,
928 "isalias": isalias,
892 "isalias": isalias,
929 "ismagic": ismagic,
893 "ismagic": ismagic,
930 "subclasses": None,
894 "subclasses": None,
931 },
895 },
932 },
896 },
933 )
897 )
934
898
935 if parents_docs:
899 if parents_docs:
936 ds = parents_docs
900 ds = parents_docs
937 elif isalias:
901 elif isalias:
938 if not callable(obj):
902 if not callable(obj):
939 try:
903 try:
940 ds = "Alias to the system command:\n %s" % obj[1]
904 ds = "Alias to the system command:\n %s" % obj[1]
941 except:
905 except:
942 ds = "Alias: " + str(obj)
906 ds = "Alias: " + str(obj)
943 else:
907 else:
944 ds = "Alias to " + str(obj)
908 ds = "Alias to " + str(obj)
945 if obj.__doc__:
909 if obj.__doc__:
946 ds += "\nDocstring:\n" + obj.__doc__
910 ds += "\nDocstring:\n" + obj.__doc__
947 else:
911 else:
948 ds_or_None = getdoc(obj)
912 ds_or_None = getdoc(obj)
949 if ds_or_None is None:
913 if ds_or_None is None:
950 ds = '<no docstring>'
914 ds = '<no docstring>'
951 else:
915 else:
952 ds = ds_or_None
916 ds = ds_or_None
953
917
954 ds = prelude + ds
918 ds = prelude + ds
955
919
956 # store output in a dict, we initialize it here and fill it as we go
920 # store output in a dict, we initialize it here and fill it as we go
957
921
958 string_max = 200 # max size of strings to show (snipped if longer)
922 string_max = 200 # max size of strings to show (snipped if longer)
959 shalf = int((string_max - 5) / 2)
923 shalf = int((string_max - 5) / 2)
960
924
961 if ismagic:
925 if ismagic:
962 out['type_name'] = 'Magic function'
926 out['type_name'] = 'Magic function'
963 elif isalias:
927 elif isalias:
964 out['type_name'] = 'System alias'
928 out['type_name'] = 'System alias'
965 else:
929 else:
966 out['type_name'] = type(obj).__name__
930 out['type_name'] = type(obj).__name__
967
931
968 try:
932 try:
969 bclass = obj.__class__
933 bclass = obj.__class__
970 out['base_class'] = str(bclass)
934 out['base_class'] = str(bclass)
971 except:
935 except:
972 pass
936 pass
973
937
974 # String form, but snip if too long in ? form (full in ??)
938 # String form, but snip if too long in ? form (full in ??)
975 if detail_level >= self.str_detail_level:
939 if detail_level >= self.str_detail_level:
976 try:
940 try:
977 ostr = str(obj)
941 ostr = str(obj)
978 if not detail_level and len(ostr) > string_max:
942 if not detail_level and len(ostr) > string_max:
979 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
943 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
980 # TODO: `'string_form'.expandtabs()` seems wrong, but
944 # TODO: `'string_form'.expandtabs()` seems wrong, but
981 # it was (nearly) like this since the first commit ever.
945 # it was (nearly) like this since the first commit ever.
982 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
946 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
983 q.strip() for q in ostr.split("\n")
947 q.strip() for q in ostr.split("\n")
984 )
948 )
985 out["string_form"] = ostr
949 out["string_form"] = ostr
986 except:
950 except:
987 pass
951 pass
988
952
989 if ospace:
953 if ospace:
990 out['namespace'] = ospace
954 out['namespace'] = ospace
991
955
992 # Length (for strings and lists)
956 # Length (for strings and lists)
993 try:
957 try:
994 out['length'] = str(len(obj))
958 out['length'] = str(len(obj))
995 except Exception:
959 except Exception:
996 pass
960 pass
997
961
998 # Filename where object was defined
962 # Filename where object was defined
999 binary_file = False
963 binary_file = False
1000 fname = find_file(obj)
964 fname = find_file(obj)
1001 if fname is None:
965 if fname is None:
1002 # if anything goes wrong, we don't want to show source, so it's as
966 # if anything goes wrong, we don't want to show source, so it's as
1003 # if the file was binary
967 # if the file was binary
1004 binary_file = True
968 binary_file = True
1005 else:
969 else:
1006 if fname.endswith(('.so', '.dll', '.pyd')):
970 if fname.endswith(('.so', '.dll', '.pyd')):
1007 binary_file = True
971 binary_file = True
1008 elif fname.endswith('<string>'):
972 elif fname.endswith('<string>'):
1009 fname = 'Dynamically generated function. No source code available.'
973 fname = 'Dynamically generated function. No source code available.'
1010 out['file'] = compress_user(fname)
974 out['file'] = compress_user(fname)
1011
975
1012 # Original source code for a callable, class or property.
976 # Original source code for a callable, class or property.
1013 if detail_level:
977 if detail_level:
1014 # Flush the source cache because inspect can return out-of-date
978 # Flush the source cache because inspect can return out-of-date
1015 # source
979 # source
1016 linecache.checkcache()
980 linecache.checkcache()
1017 try:
981 try:
1018 if isinstance(obj, property) or not binary_file:
982 if isinstance(obj, property) or not binary_file:
1019 src = getsource(obj, oname)
983 src = getsource(obj, oname)
1020 if src is not None:
984 if src is not None:
1021 src = src.rstrip()
985 src = src.rstrip()
1022 out['source'] = src
986 out['source'] = src
1023
987
1024 except Exception:
988 except Exception:
1025 pass
989 pass
1026
990
1027 # Add docstring only if no source is to be shown (avoid repetitions).
991 # Add docstring only if no source is to be shown (avoid repetitions).
1028 if ds and not self._source_contains_docstring(out.get('source'), ds):
992 if ds and not self._source_contains_docstring(out.get('source'), ds):
1029 out['docstring'] = ds
993 out['docstring'] = ds
1030
994
1031 # Constructor docstring for classes
995 # Constructor docstring for classes
1032 if inspect.isclass(obj):
996 if inspect.isclass(obj):
1033 out['isclass'] = True
997 out['isclass'] = True
1034
998
1035 # get the init signature:
999 # get the init signature:
1036 try:
1000 try:
1037 init_def = self._getdef(obj, oname)
1001 init_def = self._getdef(obj, oname)
1038 except AttributeError:
1002 except AttributeError:
1039 init_def = None
1003 init_def = None
1040
1004
1041 # get the __init__ docstring
1005 # get the __init__ docstring
1042 try:
1006 try:
1043 obj_init = obj.__init__
1007 obj_init = obj.__init__
1044 except AttributeError:
1008 except AttributeError:
1045 init_ds = None
1009 init_ds = None
1046 else:
1010 else:
1047 if init_def is None:
1011 if init_def is None:
1048 # Get signature from init if top-level sig failed.
1012 # Get signature from init if top-level sig failed.
1049 # Can happen for built-in types (list, etc.).
1013 # Can happen for built-in types (list, etc.).
1050 try:
1014 try:
1051 init_def = self._getdef(obj_init, oname)
1015 init_def = self._getdef(obj_init, oname)
1052 except AttributeError:
1016 except AttributeError:
1053 pass
1017 pass
1054 init_ds = getdoc(obj_init)
1018 init_ds = getdoc(obj_init)
1055 # Skip Python's auto-generated docstrings
1019 # Skip Python's auto-generated docstrings
1056 if init_ds == _object_init_docstring:
1020 if init_ds == _object_init_docstring:
1057 init_ds = None
1021 init_ds = None
1058
1022
1059 if init_def:
1023 if init_def:
1060 out['init_definition'] = init_def
1024 out['init_definition'] = init_def
1061
1025
1062 if init_ds:
1026 if init_ds:
1063 out['init_docstring'] = init_ds
1027 out['init_docstring'] = init_ds
1064
1028
1065 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1029 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1066 if len(names) < 10:
1030 if len(names) < 10:
1067 all_names = ', '.join(names)
1031 all_names = ', '.join(names)
1068 else:
1032 else:
1069 all_names = ', '.join(names[:10]+['...'])
1033 all_names = ', '.join(names[:10]+['...'])
1070 out['subclasses'] = all_names
1034 out['subclasses'] = all_names
1071 # and class docstring for instances:
1035 # and class docstring for instances:
1072 else:
1036 else:
1073 # reconstruct the function definition and print it:
1037 # reconstruct the function definition and print it:
1074 defln = self._getdef(obj, oname)
1038 defln = self._getdef(obj, oname)
1075 if defln:
1039 if defln:
1076 out['definition'] = defln
1040 out['definition'] = defln
1077
1041
1078 # First, check whether the instance docstring is identical to the
1042 # First, check whether the instance docstring is identical to the
1079 # class one, and print it separately if they don't coincide. In
1043 # class one, and print it separately if they don't coincide. In
1080 # most cases they will, but it's nice to print all the info for
1044 # most cases they will, but it's nice to print all the info for
1081 # objects which use instance-customized docstrings.
1045 # objects which use instance-customized docstrings.
1082 if ds:
1046 if ds:
1083 try:
1047 try:
1084 cls = getattr(obj,'__class__')
1048 cls = getattr(obj,'__class__')
1085 except:
1049 except:
1086 class_ds = None
1050 class_ds = None
1087 else:
1051 else:
1088 class_ds = getdoc(cls)
1052 class_ds = getdoc(cls)
1089 # Skip Python's auto-generated docstrings
1053 # Skip Python's auto-generated docstrings
1090 if class_ds in _builtin_type_docstrings:
1054 if class_ds in _builtin_type_docstrings:
1091 class_ds = None
1055 class_ds = None
1092 if class_ds and ds != class_ds:
1056 if class_ds and ds != class_ds:
1093 out['class_docstring'] = class_ds
1057 out['class_docstring'] = class_ds
1094
1058
1095 # Next, try to show constructor docstrings
1059 # Next, try to show constructor docstrings
1096 try:
1060 try:
1097 init_ds = getdoc(obj.__init__)
1061 init_ds = getdoc(obj.__init__)
1098 # Skip Python's auto-generated docstrings
1062 # Skip Python's auto-generated docstrings
1099 if init_ds == _object_init_docstring:
1063 if init_ds == _object_init_docstring:
1100 init_ds = None
1064 init_ds = None
1101 except AttributeError:
1065 except AttributeError:
1102 init_ds = None
1066 init_ds = None
1103 if init_ds:
1067 if init_ds:
1104 out['init_docstring'] = init_ds
1068 out['init_docstring'] = init_ds
1105
1069
1106 # Call form docstring for callable instances
1070 # Call form docstring for callable instances
1107 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1071 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1108 call_def = self._getdef(obj.__call__, oname)
1072 call_def = self._getdef(obj.__call__, oname)
1109 if call_def and (call_def != out.get('definition')):
1073 if call_def and (call_def != out.get('definition')):
1110 # it may never be the case that call def and definition differ,
1074 # it may never be the case that call def and definition differ,
1111 # but don't include the same signature twice
1075 # but don't include the same signature twice
1112 out['call_def'] = call_def
1076 out['call_def'] = call_def
1113 call_ds = getdoc(obj.__call__)
1077 call_ds = getdoc(obj.__call__)
1114 # Skip Python's auto-generated docstrings
1078 # Skip Python's auto-generated docstrings
1115 if call_ds == _func_call_docstring:
1079 if call_ds == _func_call_docstring:
1116 call_ds = None
1080 call_ds = None
1117 if call_ds:
1081 if call_ds:
1118 out['call_docstring'] = call_ds
1082 out['call_docstring'] = call_ds
1119
1083
1120 return out
1084 return out
1121
1085
1122 @staticmethod
1086 @staticmethod
1123 def _source_contains_docstring(src, doc):
1087 def _source_contains_docstring(src, doc):
1124 """
1088 """
1125 Check whether the source *src* contains the docstring *doc*.
1089 Check whether the source *src* contains the docstring *doc*.
1126
1090
1127 This is is helper function to skip displaying the docstring if the
1091 This is is helper function to skip displaying the docstring if the
1128 source already contains it, avoiding repetition of information.
1092 source already contains it, avoiding repetition of information.
1129 """
1093 """
1130 try:
1094 try:
1131 (def_node,) = ast.parse(dedent(src)).body
1095 (def_node,) = ast.parse(dedent(src)).body
1132 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1096 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1133 except Exception:
1097 except Exception:
1134 # The source can become invalid or even non-existent (because it
1098 # The source can become invalid or even non-existent (because it
1135 # is re-fetched from the source file) so the above code fail in
1099 # is re-fetched from the source file) so the above code fail in
1136 # arbitrary ways.
1100 # arbitrary ways.
1137 return False
1101 return False
1138
1102
1139 def psearch(self,pattern,ns_table,ns_search=[],
1103 def psearch(self,pattern,ns_table,ns_search=[],
1140 ignore_case=False,show_all=False, *, list_types=False):
1104 ignore_case=False,show_all=False, *, list_types=False):
1141 """Search namespaces with wildcards for objects.
1105 """Search namespaces with wildcards for objects.
1142
1106
1143 Arguments:
1107 Arguments:
1144
1108
1145 - pattern: string containing shell-like wildcards to use in namespace
1109 - pattern: string containing shell-like wildcards to use in namespace
1146 searches and optionally a type specification to narrow the search to
1110 searches and optionally a type specification to narrow the search to
1147 objects of that type.
1111 objects of that type.
1148
1112
1149 - ns_table: dict of name->namespaces for search.
1113 - ns_table: dict of name->namespaces for search.
1150
1114
1151 Optional arguments:
1115 Optional arguments:
1152
1116
1153 - ns_search: list of namespace names to include in search.
1117 - ns_search: list of namespace names to include in search.
1154
1118
1155 - ignore_case(False): make the search case-insensitive.
1119 - ignore_case(False): make the search case-insensitive.
1156
1120
1157 - show_all(False): show all names, including those starting with
1121 - show_all(False): show all names, including those starting with
1158 underscores.
1122 underscores.
1159
1123
1160 - list_types(False): list all available object types for object matching.
1124 - list_types(False): list all available object types for object matching.
1161 """
1125 """
1162 # print('ps pattern:<%r>' % pattern) # dbg
1126 # print('ps pattern:<%r>' % pattern) # dbg
1163
1127
1164 # defaults
1128 # defaults
1165 type_pattern = 'all'
1129 type_pattern = 'all'
1166 filter = ''
1130 filter = ''
1167
1131
1168 # list all object types
1132 # list all object types
1169 if list_types:
1133 if list_types:
1170 page.page('\n'.join(sorted(typestr2type)))
1134 page.page('\n'.join(sorted(typestr2type)))
1171 return
1135 return
1172
1136
1173 cmds = pattern.split()
1137 cmds = pattern.split()
1174 len_cmds = len(cmds)
1138 len_cmds = len(cmds)
1175 if len_cmds == 1:
1139 if len_cmds == 1:
1176 # Only filter pattern given
1140 # Only filter pattern given
1177 filter = cmds[0]
1141 filter = cmds[0]
1178 elif len_cmds == 2:
1142 elif len_cmds == 2:
1179 # Both filter and type specified
1143 # Both filter and type specified
1180 filter,type_pattern = cmds
1144 filter,type_pattern = cmds
1181 else:
1145 else:
1182 raise ValueError('invalid argument string for psearch: <%s>' %
1146 raise ValueError('invalid argument string for psearch: <%s>' %
1183 pattern)
1147 pattern)
1184
1148
1185 # filter search namespaces
1149 # filter search namespaces
1186 for name in ns_search:
1150 for name in ns_search:
1187 if name not in ns_table:
1151 if name not in ns_table:
1188 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1152 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1189 (name,ns_table.keys()))
1153 (name,ns_table.keys()))
1190
1154
1191 # print('type_pattern:',type_pattern) # dbg
1155 # print('type_pattern:',type_pattern) # dbg
1192 search_result, namespaces_seen = set(), set()
1156 search_result, namespaces_seen = set(), set()
1193 for ns_name in ns_search:
1157 for ns_name in ns_search:
1194 ns = ns_table[ns_name]
1158 ns = ns_table[ns_name]
1195 # Normally, locals and globals are the same, so we just check one.
1159 # Normally, locals and globals are the same, so we just check one.
1196 if id(ns) in namespaces_seen:
1160 if id(ns) in namespaces_seen:
1197 continue
1161 continue
1198 namespaces_seen.add(id(ns))
1162 namespaces_seen.add(id(ns))
1199 tmp_res = list_namespace(ns, type_pattern, filter,
1163 tmp_res = list_namespace(ns, type_pattern, filter,
1200 ignore_case=ignore_case, show_all=show_all)
1164 ignore_case=ignore_case, show_all=show_all)
1201 search_result.update(tmp_res)
1165 search_result.update(tmp_res)
1202
1166
1203 page.page('\n'.join(sorted(search_result)))
1167 page.page('\n'.join(sorted(search_result)))
1204
1168
1205
1169
1206 def _render_signature(obj_signature, obj_name) -> str:
1170 def _render_signature(obj_signature, obj_name) -> str:
1207 """
1171 """
1208 This was mostly taken from inspect.Signature.__str__.
1172 This was mostly taken from inspect.Signature.__str__.
1209 Look there for the comments.
1173 Look there for the comments.
1210 The only change is to add linebreaks when this gets too long.
1174 The only change is to add linebreaks when this gets too long.
1211 """
1175 """
1212 result = []
1176 result = []
1213 pos_only = False
1177 pos_only = False
1214 kw_only = True
1178 kw_only = True
1215 for param in obj_signature.parameters.values():
1179 for param in obj_signature.parameters.values():
1216 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1180 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1217 pos_only = True
1181 pos_only = True
1218 elif pos_only:
1182 elif pos_only:
1219 result.append('/')
1183 result.append('/')
1220 pos_only = False
1184 pos_only = False
1221
1185
1222 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1186 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1223 kw_only = False
1187 kw_only = False
1224 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1188 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1225 result.append('*')
1189 result.append('*')
1226 kw_only = False
1190 kw_only = False
1227
1191
1228 result.append(str(param))
1192 result.append(str(param))
1229
1193
1230 if pos_only:
1194 if pos_only:
1231 result.append('/')
1195 result.append('/')
1232
1196
1233 # add up name, parameters, braces (2), and commas
1197 # add up name, parameters, braces (2), and commas
1234 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1198 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1235 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1199 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1236 rendered = '{}(\n{})'.format(obj_name, ''.join(
1200 rendered = '{}(\n{})'.format(obj_name, ''.join(
1237 ' {},\n'.format(r) for r in result)
1201 ' {},\n'.format(r) for r in result)
1238 )
1202 )
1239 else:
1203 else:
1240 rendered = '{}({})'.format(obj_name, ', '.join(result))
1204 rendered = '{}({})'.format(obj_name, ', '.join(result))
1241
1205
1242 if obj_signature.return_annotation is not inspect._empty:
1206 if obj_signature.return_annotation is not inspect._empty:
1243 anno = inspect.formatannotation(obj_signature.return_annotation)
1207 anno = inspect.formatannotation(obj_signature.return_annotation)
1244 rendered += ' -> {}'.format(anno)
1208 rendered += ' -> {}'.format(anno)
1245
1209
1246 return rendered
1210 return rendered
@@ -1,1030 +1,1023
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 inspect
5 import inspect
6 from warnings import warn
6 from warnings import warn
7 from typing import Union as UnionType, Optional
7 from typing import Union as UnionType, Optional
8
8
9 from IPython.core.async_helpers import get_asyncio_loop
9 from IPython.core.async_helpers import get_asyncio_loop
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
11 from IPython.utils.py3compat import input
11 from IPython.utils.py3compat import input
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import (
14 from traitlets import (
15 Any,
15 Bool,
16 Bool,
16 Unicode,
17 Dict,
17 Dict,
18 Enum,
19 Float,
20 Instance,
18 Integer,
21 Integer,
19 List,
22 List,
20 observe,
21 Instance,
22 Type,
23 Type,
23 default,
24 Unicode,
24 Enum,
25 Union,
25 Union,
26 Any,
26 default,
27 observe,
27 validate,
28 validate,
28 Float,
29 )
29 )
30
30
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
34 from prompt_toolkit.formatted_text import PygmentsTokens
34 from prompt_toolkit.formatted_text import PygmentsTokens
35 from prompt_toolkit.history import History
35 from prompt_toolkit.history import History
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
37 from prompt_toolkit.output import ColorDepth
37 from prompt_toolkit.output import ColorDepth
38 from prompt_toolkit.patch_stdout import patch_stdout
38 from prompt_toolkit.patch_stdout import patch_stdout
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
42 from prompt_toolkit import __version__ as ptk_version
42 from prompt_toolkit import __version__ as ptk_version
43
43
44 from pygments.styles import get_style_by_name
44 from pygments.styles import get_style_by_name
45 from pygments.style import Style
45 from pygments.style import Style
46 from pygments.token import Token
46 from pygments.token import Token
47
47
48 from .debugger import TerminalPdb, Pdb
48 from .debugger import TerminalPdb, Pdb
49 from .magics import TerminalMagics
49 from .magics import TerminalMagics
50 from .pt_inputhooks import get_inputhook_name_and_func
50 from .pt_inputhooks import get_inputhook_name_and_func
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
53 from .shortcuts import (
53 from .shortcuts import (
54 KEY_BINDINGS,
54 KEY_BINDINGS,
55 UNASSIGNED_ALLOWED_COMMANDS,
55 UNASSIGNED_ALLOWED_COMMANDS,
56 create_ipython_shortcuts,
56 create_ipython_shortcuts,
57 create_identifier,
57 create_identifier,
58 RuntimeBinding,
58 RuntimeBinding,
59 add_binding,
59 add_binding,
60 )
60 )
61 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
61 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
62 from .shortcuts.auto_suggest import (
62 from .shortcuts.auto_suggest import (
63 NavigableAutoSuggestFromHistory,
63 NavigableAutoSuggestFromHistory,
64 AppendAutoSuggestionInAnyLine,
64 AppendAutoSuggestionInAnyLine,
65 )
65 )
66
66
67 PTK3 = ptk_version.startswith('3.')
67 PTK3 = ptk_version.startswith('3.')
68
68
69
69
70 class _NoStyle(Style):
70 class _NoStyle(Style):
71 pass
71 pass
72
72
73
73
74 _style_overrides_light_bg = {
74 _style_overrides_light_bg = {
75 Token.Prompt: '#ansibrightblue',
75 Token.Prompt: '#ansibrightblue',
76 Token.PromptNum: '#ansiblue bold',
76 Token.PromptNum: '#ansiblue bold',
77 Token.OutPrompt: '#ansibrightred',
77 Token.OutPrompt: '#ansibrightred',
78 Token.OutPromptNum: '#ansired bold',
78 Token.OutPromptNum: '#ansired bold',
79 }
79 }
80
80
81 _style_overrides_linux = {
81 _style_overrides_linux = {
82 Token.Prompt: '#ansibrightgreen',
82 Token.Prompt: '#ansibrightgreen',
83 Token.PromptNum: '#ansigreen bold',
83 Token.PromptNum: '#ansigreen bold',
84 Token.OutPrompt: '#ansibrightred',
84 Token.OutPrompt: '#ansibrightred',
85 Token.OutPromptNum: '#ansired bold',
85 Token.OutPromptNum: '#ansired bold',
86 }
86 }
87
87
88
88
89 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
89 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
90 """
90 """
91 Sagemath use custom prompt and we broke them in 8.19.
91 Sagemath use custom prompt and we broke them in 8.19.
92 """
92 """
93 sig = inspect.signature(method)
93 sig = inspect.signature(method)
94 if "lineno" in inspect.signature(method).parameters or any(
94 if "lineno" in inspect.signature(method).parameters or any(
95 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
95 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
96 ):
96 ):
97 return method(width, lineno=lineno)
97 return method(width, lineno=lineno)
98 else:
98 else:
99 return method(width)
99 return method(width)
100
100
101
101
102 def get_default_editor():
102 def get_default_editor():
103 try:
103 try:
104 return os.environ['EDITOR']
104 return os.environ['EDITOR']
105 except KeyError:
105 except KeyError:
106 pass
106 pass
107 except UnicodeError:
107 except UnicodeError:
108 warn("$EDITOR environment variable is not pure ASCII. Using platform "
108 warn("$EDITOR environment variable is not pure ASCII. Using platform "
109 "default editor.")
109 "default editor.")
110
110
111 if os.name == 'posix':
111 if os.name == 'posix':
112 return 'vi' # the only one guaranteed to be there!
112 return 'vi' # the only one guaranteed to be there!
113 else:
113 else:
114 return "notepad" # same in Windows!
114 return "notepad" # same in Windows!
115
115
116
116
117 # conservatively check for tty
117 # conservatively check for tty
118 # overridden streams can result in things like:
118 # overridden streams can result in things like:
119 # - sys.stdin = None
119 # - sys.stdin = None
120 # - no isatty method
120 # - no isatty method
121 for _name in ('stdin', 'stdout', 'stderr'):
121 for _name in ('stdin', 'stdout', 'stderr'):
122 _stream = getattr(sys, _name)
122 _stream = getattr(sys, _name)
123 try:
123 try:
124 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
124 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
125 _is_tty = False
125 _is_tty = False
126 break
126 break
127 except ValueError:
127 except ValueError:
128 # stream is closed
128 # stream is closed
129 _is_tty = False
129 _is_tty = False
130 break
130 break
131 else:
131 else:
132 _is_tty = True
132 _is_tty = True
133
133
134
134
135 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
135 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
136
136
137 def black_reformat_handler(text_before_cursor):
137 def black_reformat_handler(text_before_cursor):
138 """
138 """
139 We do not need to protect against error,
139 We do not need to protect against error,
140 this is taken care at a higher level where any reformat error is ignored.
140 this is taken care at a higher level where any reformat error is ignored.
141 Indeed we may call reformatting on incomplete code.
141 Indeed we may call reformatting on incomplete code.
142 """
142 """
143 import black
143 import black
144
144
145 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
145 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
146 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
146 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
147 formatted_text = formatted_text[:-1]
147 formatted_text = formatted_text[:-1]
148 return formatted_text
148 return formatted_text
149
149
150
150
151 def yapf_reformat_handler(text_before_cursor):
151 def yapf_reformat_handler(text_before_cursor):
152 from yapf.yapflib import file_resources
152 from yapf.yapflib import file_resources
153 from yapf.yapflib import yapf_api
153 from yapf.yapflib import yapf_api
154
154
155 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
155 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
156 formatted_text, was_formatted = yapf_api.FormatCode(
156 formatted_text, was_formatted = yapf_api.FormatCode(
157 text_before_cursor, style_config=style_config
157 text_before_cursor, style_config=style_config
158 )
158 )
159 if was_formatted:
159 if was_formatted:
160 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
160 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
161 formatted_text = formatted_text[:-1]
161 formatted_text = formatted_text[:-1]
162 return formatted_text
162 return formatted_text
163 else:
163 else:
164 return text_before_cursor
164 return text_before_cursor
165
165
166
166
167 class PtkHistoryAdapter(History):
167 class PtkHistoryAdapter(History):
168 """
168 """
169 Prompt toolkit has it's own way of handling history, Where it assumes it can
169 Prompt toolkit has it's own way of handling history, Where it assumes it can
170 Push/pull from history.
170 Push/pull from history.
171
171
172 """
172 """
173
173
174 def __init__(self, shell):
174 def __init__(self, shell):
175 super().__init__()
175 super().__init__()
176 self.shell = shell
176 self.shell = shell
177 self._refresh()
177 self._refresh()
178
178
179 def append_string(self, string):
179 def append_string(self, string):
180 # we rely on sql for that.
180 # we rely on sql for that.
181 self._loaded = False
181 self._loaded = False
182 self._refresh()
182 self._refresh()
183
183
184 def _refresh(self):
184 def _refresh(self):
185 if not self._loaded:
185 if not self._loaded:
186 self._loaded_strings = list(self.load_history_strings())
186 self._loaded_strings = list(self.load_history_strings())
187
187
188 def load_history_strings(self):
188 def load_history_strings(self):
189 last_cell = ""
189 last_cell = ""
190 res = []
190 res = []
191 for __, ___, cell in self.shell.history_manager.get_tail(
191 for __, ___, cell in self.shell.history_manager.get_tail(
192 self.shell.history_load_length, include_latest=True
192 self.shell.history_load_length, include_latest=True
193 ):
193 ):
194 # Ignore blank lines and consecutive duplicates
194 # Ignore blank lines and consecutive duplicates
195 cell = cell.rstrip()
195 cell = cell.rstrip()
196 if cell and (cell != last_cell):
196 if cell and (cell != last_cell):
197 res.append(cell)
197 res.append(cell)
198 last_cell = cell
198 last_cell = cell
199 yield from res[::-1]
199 yield from res[::-1]
200
200
201 def store_string(self, string: str) -> None:
201 def store_string(self, string: str) -> None:
202 pass
202 pass
203
203
204 class TerminalInteractiveShell(InteractiveShell):
204 class TerminalInteractiveShell(InteractiveShell):
205 mime_renderers = Dict().tag(config=True)
205 mime_renderers = Dict().tag(config=True)
206
206
207 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
207 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
208 'to reserve for the tab completion menu, '
208 'to reserve for the tab completion menu, '
209 'search history, ...etc, the height of '
209 'search history, ...etc, the height of '
210 'these menus will at most this value. '
210 'these menus will at most this value. '
211 'Increase it is you prefer long and skinny '
211 'Increase it is you prefer long and skinny '
212 'menus, decrease for short and wide.'
212 'menus, decrease for short and wide.'
213 ).tag(config=True)
213 ).tag(config=True)
214
214
215 pt_app: UnionType[PromptSession, None] = None
215 pt_app: UnionType[PromptSession, None] = None
216 auto_suggest: UnionType[
216 auto_suggest: UnionType[
217 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
217 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
218 ] = None
218 ] = None
219 debugger_history = None
219 debugger_history = None
220
220
221 debugger_history_file = Unicode(
221 debugger_history_file = Unicode(
222 "~/.pdbhistory", help="File in which to store and read history"
222 "~/.pdbhistory", help="File in which to store and read history"
223 ).tag(config=True)
223 ).tag(config=True)
224
224
225 simple_prompt = Bool(_use_simple_prompt,
225 simple_prompt = Bool(_use_simple_prompt,
226 help="""Use `raw_input` for the REPL, without completion and prompt colors.
226 help="""Use `raw_input` for the REPL, without completion and prompt colors.
227
227
228 Useful when controlling IPython as a subprocess, and piping
228 Useful when controlling IPython as a subprocess, and piping
229 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
229 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
230 and emacs' inferior-python subprocess (assuming you have set
230 and emacs' inferior-python subprocess (assuming you have set
231 `python-shell-interpreter` to "ipython") available through the
231 `python-shell-interpreter` to "ipython") available through the
232 built-in `M-x run-python` and third party packages such as elpy.
232 built-in `M-x run-python` and third party packages such as elpy.
233
233
234 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
234 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
235 environment variable is set, or the current terminal is not a tty.
235 environment variable is set, or the current terminal is not a tty.
236 Thus the Default value reported in --help-all, or config will often
236 Thus the Default value reported in --help-all, or config will often
237 be incorrectly reported.
237 be incorrectly reported.
238 """,
238 """,
239 ).tag(config=True)
239 ).tag(config=True)
240
240
241 @property
241 @property
242 def debugger_cls(self):
242 def debugger_cls(self):
243 return Pdb if self.simple_prompt else TerminalPdb
243 return Pdb if self.simple_prompt else TerminalPdb
244
244
245 confirm_exit = Bool(True,
245 confirm_exit = Bool(True,
246 help="""
246 help="""
247 Set to confirm when you try to exit IPython with an EOF (Control-D
247 Set to confirm when you try to exit IPython with an EOF (Control-D
248 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
248 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
249 you can force a direct exit without any confirmation.""",
249 you can force a direct exit without any confirmation.""",
250 ).tag(config=True)
250 ).tag(config=True)
251
251
252 editing_mode = Unicode('emacs',
252 editing_mode = Unicode('emacs',
253 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
253 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
254 ).tag(config=True)
254 ).tag(config=True)
255
255
256 emacs_bindings_in_vi_insert_mode = Bool(
256 emacs_bindings_in_vi_insert_mode = Bool(
257 True,
257 True,
258 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
258 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
259 ).tag(config=True)
259 ).tag(config=True)
260
260
261 modal_cursor = Bool(
261 modal_cursor = Bool(
262 True,
262 True,
263 help="""
263 help="""
264 Cursor shape changes depending on vi mode: beam in vi insert mode,
264 Cursor shape changes depending on vi mode: beam in vi insert mode,
265 block in nav mode, underscore in replace mode.""",
265 block in nav mode, underscore in replace mode.""",
266 ).tag(config=True)
266 ).tag(config=True)
267
267
268 ttimeoutlen = Float(
268 ttimeoutlen = Float(
269 0.01,
269 0.01,
270 help="""The time in milliseconds that is waited for a key code
270 help="""The time in milliseconds that is waited for a key code
271 to complete.""",
271 to complete.""",
272 ).tag(config=True)
272 ).tag(config=True)
273
273
274 timeoutlen = Float(
274 timeoutlen = Float(
275 0.5,
275 0.5,
276 help="""The time in milliseconds that is waited for a mapped key
276 help="""The time in milliseconds that is waited for a mapped key
277 sequence to complete.""",
277 sequence to complete.""",
278 ).tag(config=True)
278 ).tag(config=True)
279
279
280 autoformatter = Unicode(
280 autoformatter = Unicode(
281 None,
281 None,
282 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
282 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
283 allow_none=True
283 allow_none=True
284 ).tag(config=True)
284 ).tag(config=True)
285
285
286 auto_match = Bool(
286 auto_match = Bool(
287 False,
287 False,
288 help="""
288 help="""
289 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
289 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
290 Brackets: (), [], {}
290 Brackets: (), [], {}
291 Quotes: '', \"\"
291 Quotes: '', \"\"
292 """,
292 """,
293 ).tag(config=True)
293 ).tag(config=True)
294
294
295 mouse_support = Bool(False,
295 mouse_support = Bool(False,
296 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
296 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
297 ).tag(config=True)
297 ).tag(config=True)
298
298
299 # We don't load the list of styles for the help string, because loading
299 # We don't load the list of styles for the help string, because loading
300 # Pygments plugins takes time and can cause unexpected errors.
300 # Pygments plugins takes time and can cause unexpected errors.
301 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
301 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
302 help="""The name or class of a Pygments style to use for syntax
302 help="""The name or class of a Pygments style to use for syntax
303 highlighting. To see available styles, run `pygmentize -L styles`."""
303 highlighting. To see available styles, run `pygmentize -L styles`."""
304 ).tag(config=True)
304 ).tag(config=True)
305
305
306 @validate('editing_mode')
306 @validate('editing_mode')
307 def _validate_editing_mode(self, proposal):
307 def _validate_editing_mode(self, proposal):
308 if proposal['value'].lower() == 'vim':
308 if proposal['value'].lower() == 'vim':
309 proposal['value']= 'vi'
309 proposal['value']= 'vi'
310 elif proposal['value'].lower() == 'default':
310 elif proposal['value'].lower() == 'default':
311 proposal['value']= 'emacs'
311 proposal['value']= 'emacs'
312
312
313 if hasattr(EditingMode, proposal['value'].upper()):
313 if hasattr(EditingMode, proposal['value'].upper()):
314 return proposal['value'].lower()
314 return proposal['value'].lower()
315
315
316 return self.editing_mode
316 return self.editing_mode
317
317
318 @observe('editing_mode')
318 @observe('editing_mode')
319 def _editing_mode(self, change):
319 def _editing_mode(self, change):
320 if self.pt_app:
320 if self.pt_app:
321 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
321 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
322
322
323 def _set_formatter(self, formatter):
323 def _set_formatter(self, formatter):
324 if formatter is None:
324 if formatter is None:
325 self.reformat_handler = lambda x:x
325 self.reformat_handler = lambda x:x
326 elif formatter == 'black':
326 elif formatter == 'black':
327 self.reformat_handler = black_reformat_handler
327 self.reformat_handler = black_reformat_handler
328 elif formatter == "yapf":
328 elif formatter == "yapf":
329 self.reformat_handler = yapf_reformat_handler
329 self.reformat_handler = yapf_reformat_handler
330 else:
330 else:
331 raise ValueError
331 raise ValueError
332
332
333 @observe("autoformatter")
333 @observe("autoformatter")
334 def _autoformatter_changed(self, change):
334 def _autoformatter_changed(self, change):
335 formatter = change.new
335 formatter = change.new
336 self._set_formatter(formatter)
336 self._set_formatter(formatter)
337
337
338 @observe('highlighting_style')
338 @observe('highlighting_style')
339 @observe('colors')
339 @observe('colors')
340 def _highlighting_style_changed(self, change):
340 def _highlighting_style_changed(self, change):
341 self.refresh_style()
341 self.refresh_style()
342
342
343 def refresh_style(self):
343 def refresh_style(self):
344 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
344 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
345
345
346 highlighting_style_overrides = Dict(
346 highlighting_style_overrides = Dict(
347 help="Override highlighting format for specific tokens"
347 help="Override highlighting format for specific tokens"
348 ).tag(config=True)
348 ).tag(config=True)
349
349
350 true_color = Bool(False,
350 true_color = Bool(False,
351 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
351 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
352 If your terminal supports true color, the following command should
352 If your terminal supports true color, the following command should
353 print ``TRUECOLOR`` in orange::
353 print ``TRUECOLOR`` in orange::
354
354
355 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
355 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
356 """,
356 """,
357 ).tag(config=True)
357 ).tag(config=True)
358
358
359 editor = Unicode(get_default_editor(),
359 editor = Unicode(get_default_editor(),
360 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
360 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
361 ).tag(config=True)
361 ).tag(config=True)
362
362
363 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
363 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
364
364
365 prompts = Instance(Prompts)
365 prompts = Instance(Prompts)
366
366
367 @default('prompts')
367 @default('prompts')
368 def _prompts_default(self):
368 def _prompts_default(self):
369 return self.prompts_class(self)
369 return self.prompts_class(self)
370
370
371 # @observe('prompts')
371 # @observe('prompts')
372 # def _(self, change):
372 # def _(self, change):
373 # self._update_layout()
373 # self._update_layout()
374
374
375 @default('displayhook_class')
375 @default('displayhook_class')
376 def _displayhook_class_default(self):
376 def _displayhook_class_default(self):
377 return RichPromptDisplayHook
377 return RichPromptDisplayHook
378
378
379 term_title = Bool(True,
379 term_title = Bool(True,
380 help="Automatically set the terminal title"
380 help="Automatically set the terminal title"
381 ).tag(config=True)
381 ).tag(config=True)
382
382
383 term_title_format = Unicode("IPython: {cwd}",
383 term_title_format = Unicode("IPython: {cwd}",
384 help="Customize the terminal title format. This is a python format string. " +
384 help="Customize the terminal title format. This is a python format string. " +
385 "Available substitutions are: {cwd}."
385 "Available substitutions are: {cwd}."
386 ).tag(config=True)
386 ).tag(config=True)
387
387
388 display_completions = Enum(('column', 'multicolumn','readlinelike'),
388 display_completions = Enum(('column', 'multicolumn','readlinelike'),
389 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
389 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
390 "'readlinelike'. These options are for `prompt_toolkit`, see "
390 "'readlinelike'. These options are for `prompt_toolkit`, see "
391 "`prompt_toolkit` documentation for more information."
391 "`prompt_toolkit` documentation for more information."
392 ),
392 ),
393 default_value='multicolumn').tag(config=True)
393 default_value='multicolumn').tag(config=True)
394
394
395 highlight_matching_brackets = Bool(True,
395 highlight_matching_brackets = Bool(True,
396 help="Highlight matching brackets.",
396 help="Highlight matching brackets.",
397 ).tag(config=True)
397 ).tag(config=True)
398
398
399 extra_open_editor_shortcuts = Bool(False,
399 extra_open_editor_shortcuts = Bool(False,
400 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
400 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
401 "This is in addition to the F2 binding, which is always enabled."
401 "This is in addition to the F2 binding, which is always enabled."
402 ).tag(config=True)
402 ).tag(config=True)
403
403
404 handle_return = Any(None,
404 handle_return = Any(None,
405 help="Provide an alternative handler to be called when the user presses "
405 help="Provide an alternative handler to be called when the user presses "
406 "Return. This is an advanced option intended for debugging, which "
406 "Return. This is an advanced option intended for debugging, which "
407 "may be changed or removed in later releases."
407 "may be changed or removed in later releases."
408 ).tag(config=True)
408 ).tag(config=True)
409
409
410 enable_history_search = Bool(True,
410 enable_history_search = Bool(True,
411 help="Allows to enable/disable the prompt toolkit history search"
411 help="Allows to enable/disable the prompt toolkit history search"
412 ).tag(config=True)
412 ).tag(config=True)
413
413
414 autosuggestions_provider = Unicode(
414 autosuggestions_provider = Unicode(
415 "NavigableAutoSuggestFromHistory",
415 "NavigableAutoSuggestFromHistory",
416 help="Specifies from which source automatic suggestions are provided. "
416 help="Specifies from which source automatic suggestions are provided. "
417 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
417 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
418 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
418 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
419 " or ``None`` to disable automatic suggestions. "
419 " or ``None`` to disable automatic suggestions. "
420 "Default is `'NavigableAutoSuggestFromHistory`'.",
420 "Default is `'NavigableAutoSuggestFromHistory`'.",
421 allow_none=True,
421 allow_none=True,
422 ).tag(config=True)
422 ).tag(config=True)
423
423
424 def _set_autosuggestions(self, provider):
424 def _set_autosuggestions(self, provider):
425 # disconnect old handler
425 # disconnect old handler
426 if self.auto_suggest and isinstance(
426 if self.auto_suggest and isinstance(
427 self.auto_suggest, NavigableAutoSuggestFromHistory
427 self.auto_suggest, NavigableAutoSuggestFromHistory
428 ):
428 ):
429 self.auto_suggest.disconnect()
429 self.auto_suggest.disconnect()
430 if provider is None:
430 if provider is None:
431 self.auto_suggest = None
431 self.auto_suggest = None
432 elif provider == "AutoSuggestFromHistory":
432 elif provider == "AutoSuggestFromHistory":
433 self.auto_suggest = AutoSuggestFromHistory()
433 self.auto_suggest = AutoSuggestFromHistory()
434 elif provider == "NavigableAutoSuggestFromHistory":
434 elif provider == "NavigableAutoSuggestFromHistory":
435 self.auto_suggest = NavigableAutoSuggestFromHistory()
435 self.auto_suggest = NavigableAutoSuggestFromHistory()
436 else:
436 else:
437 raise ValueError("No valid provider.")
437 raise ValueError("No valid provider.")
438 if self.pt_app:
438 if self.pt_app:
439 self.pt_app.auto_suggest = self.auto_suggest
439 self.pt_app.auto_suggest = self.auto_suggest
440
440
441 @observe("autosuggestions_provider")
441 @observe("autosuggestions_provider")
442 def _autosuggestions_provider_changed(self, change):
442 def _autosuggestions_provider_changed(self, change):
443 provider = change.new
443 provider = change.new
444 self._set_autosuggestions(provider)
444 self._set_autosuggestions(provider)
445
445
446 shortcuts = List(
446 shortcuts = List(
447 trait=Dict(
447 trait=Dict(
448 key_trait=Enum(
448 key_trait=Enum(
449 [
449 [
450 "command",
450 "command",
451 "match_keys",
451 "match_keys",
452 "match_filter",
452 "match_filter",
453 "new_keys",
453 "new_keys",
454 "new_filter",
454 "new_filter",
455 "create",
455 "create",
456 ]
456 ]
457 ),
457 ),
458 per_key_traits={
458 per_key_traits={
459 "command": Unicode(),
459 "command": Unicode(),
460 "match_keys": List(Unicode()),
460 "match_keys": List(Unicode()),
461 "match_filter": Unicode(),
461 "match_filter": Unicode(),
462 "new_keys": List(Unicode()),
462 "new_keys": List(Unicode()),
463 "new_filter": Unicode(),
463 "new_filter": Unicode(),
464 "create": Bool(False),
464 "create": Bool(False),
465 },
465 },
466 ),
466 ),
467 help="""Add, disable or modifying shortcuts.
467 help="""Add, disable or modifying shortcuts.
468
468
469 Each entry on the list should be a dictionary with ``command`` key
469 Each entry on the list should be a dictionary with ``command`` key
470 identifying the target function executed by the shortcut and at least
470 identifying the target function executed by the shortcut and at least
471 one of the following:
471 one of the following:
472
472
473 - ``match_keys``: list of keys used to match an existing shortcut,
473 - ``match_keys``: list of keys used to match an existing shortcut,
474 - ``match_filter``: shortcut filter used to match an existing shortcut,
474 - ``match_filter``: shortcut filter used to match an existing shortcut,
475 - ``new_keys``: list of keys to set,
475 - ``new_keys``: list of keys to set,
476 - ``new_filter``: a new shortcut filter to set
476 - ``new_filter``: a new shortcut filter to set
477
477
478 The filters have to be composed of pre-defined verbs and joined by one
478 The filters have to be composed of pre-defined verbs and joined by one
479 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
479 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
480 The pre-defined verbs are:
480 The pre-defined verbs are:
481
481
482 {}
482 {}
483
483
484
484
485 To disable a shortcut set ``new_keys`` to an empty list.
485 To disable a shortcut set ``new_keys`` to an empty list.
486 To add a shortcut add key ``create`` with value ``True``.
486 To add a shortcut add key ``create`` with value ``True``.
487
487
488 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
488 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
489 be omitted if the provided specification uniquely identifies a shortcut
489 be omitted if the provided specification uniquely identifies a shortcut
490 to be modified/disabled. When modifying a shortcut ``new_filter`` or
490 to be modified/disabled. When modifying a shortcut ``new_filter`` or
491 ``new_keys`` can be omitted which will result in reuse of the existing
491 ``new_keys`` can be omitted which will result in reuse of the existing
492 filter/keys.
492 filter/keys.
493
493
494 Only shortcuts defined in IPython (and not default prompt-toolkit
494 Only shortcuts defined in IPython (and not default prompt-toolkit
495 shortcuts) can be modified or disabled. The full list of shortcuts,
495 shortcuts) can be modified or disabled. The full list of shortcuts,
496 command identifiers and filters is available under
496 command identifiers and filters is available under
497 :ref:`terminal-shortcuts-list`.
497 :ref:`terminal-shortcuts-list`.
498 """.format(
498 """.format(
499 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
499 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
500 ),
500 ),
501 ).tag(config=True)
501 ).tag(config=True)
502
502
503 @observe("shortcuts")
503 @observe("shortcuts")
504 def _shortcuts_changed(self, change):
504 def _shortcuts_changed(self, change):
505 if self.pt_app:
505 if self.pt_app:
506 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
506 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
507
507
508 def _merge_shortcuts(self, user_shortcuts):
508 def _merge_shortcuts(self, user_shortcuts):
509 # rebuild the bindings list from scratch
509 # rebuild the bindings list from scratch
510 key_bindings = create_ipython_shortcuts(self)
510 key_bindings = create_ipython_shortcuts(self)
511
511
512 # for now we only allow adding shortcuts for a specific set of
512 # for now we only allow adding shortcuts for a specific set of
513 # commands; this is a security precution.
513 # commands; this is a security precution.
514 allowed_commands = {
514 allowed_commands = {
515 create_identifier(binding.command): binding.command
515 create_identifier(binding.command): binding.command
516 for binding in KEY_BINDINGS
516 for binding in KEY_BINDINGS
517 }
517 }
518 allowed_commands.update(
518 allowed_commands.update(
519 {
519 {
520 create_identifier(command): command
520 create_identifier(command): command
521 for command in UNASSIGNED_ALLOWED_COMMANDS
521 for command in UNASSIGNED_ALLOWED_COMMANDS
522 }
522 }
523 )
523 )
524 shortcuts_to_skip = []
524 shortcuts_to_skip = []
525 shortcuts_to_add = []
525 shortcuts_to_add = []
526
526
527 for shortcut in user_shortcuts:
527 for shortcut in user_shortcuts:
528 command_id = shortcut["command"]
528 command_id = shortcut["command"]
529 if command_id not in allowed_commands:
529 if command_id not in allowed_commands:
530 allowed_commands = "\n - ".join(allowed_commands)
530 allowed_commands = "\n - ".join(allowed_commands)
531 raise ValueError(
531 raise ValueError(
532 f"{command_id} is not a known shortcut command."
532 f"{command_id} is not a known shortcut command."
533 f" Allowed commands are: \n - {allowed_commands}"
533 f" Allowed commands are: \n - {allowed_commands}"
534 )
534 )
535 old_keys = shortcut.get("match_keys", None)
535 old_keys = shortcut.get("match_keys", None)
536 old_filter = (
536 old_filter = (
537 filter_from_string(shortcut["match_filter"])
537 filter_from_string(shortcut["match_filter"])
538 if "match_filter" in shortcut
538 if "match_filter" in shortcut
539 else None
539 else None
540 )
540 )
541 matching = [
541 matching = [
542 binding
542 binding
543 for binding in KEY_BINDINGS
543 for binding in KEY_BINDINGS
544 if (
544 if (
545 (old_filter is None or binding.filter == old_filter)
545 (old_filter is None or binding.filter == old_filter)
546 and (old_keys is None or [k for k in binding.keys] == old_keys)
546 and (old_keys is None or [k for k in binding.keys] == old_keys)
547 and create_identifier(binding.command) == command_id
547 and create_identifier(binding.command) == command_id
548 )
548 )
549 ]
549 ]
550
550
551 new_keys = shortcut.get("new_keys", None)
551 new_keys = shortcut.get("new_keys", None)
552 new_filter = shortcut.get("new_filter", None)
552 new_filter = shortcut.get("new_filter", None)
553
553
554 command = allowed_commands[command_id]
554 command = allowed_commands[command_id]
555
555
556 creating_new = shortcut.get("create", False)
556 creating_new = shortcut.get("create", False)
557 modifying_existing = not creating_new and (
557 modifying_existing = not creating_new and (
558 new_keys is not None or new_filter
558 new_keys is not None or new_filter
559 )
559 )
560
560
561 if creating_new and new_keys == []:
561 if creating_new and new_keys == []:
562 raise ValueError("Cannot add a shortcut without keys")
562 raise ValueError("Cannot add a shortcut without keys")
563
563
564 if modifying_existing:
564 if modifying_existing:
565 specification = {
565 specification = {
566 key: shortcut[key]
566 key: shortcut[key]
567 for key in ["command", "filter"]
567 for key in ["command", "filter"]
568 if key in shortcut
568 if key in shortcut
569 }
569 }
570 if len(matching) == 0:
570 if len(matching) == 0:
571 raise ValueError(
571 raise ValueError(
572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
572 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
573 )
573 )
574 elif len(matching) > 1:
574 elif len(matching) > 1:
575 raise ValueError(
575 raise ValueError(
576 f"Multiple shortcuts matching {specification} found,"
576 f"Multiple shortcuts matching {specification} found,"
577 f" please add keys/filter to select one of: {matching}"
577 f" please add keys/filter to select one of: {matching}"
578 )
578 )
579
579
580 matched = matching[0]
580 matched = matching[0]
581 old_filter = matched.filter
581 old_filter = matched.filter
582 old_keys = list(matched.keys)
582 old_keys = list(matched.keys)
583 shortcuts_to_skip.append(
583 shortcuts_to_skip.append(
584 RuntimeBinding(
584 RuntimeBinding(
585 command,
585 command,
586 keys=old_keys,
586 keys=old_keys,
587 filter=old_filter,
587 filter=old_filter,
588 )
588 )
589 )
589 )
590
590
591 if new_keys != []:
591 if new_keys != []:
592 shortcuts_to_add.append(
592 shortcuts_to_add.append(
593 RuntimeBinding(
593 RuntimeBinding(
594 command,
594 command,
595 keys=new_keys or old_keys,
595 keys=new_keys or old_keys,
596 filter=(
596 filter=(
597 filter_from_string(new_filter)
597 filter_from_string(new_filter)
598 if new_filter is not None
598 if new_filter is not None
599 else (
599 else (
600 old_filter
600 old_filter
601 if old_filter is not None
601 if old_filter is not None
602 else filter_from_string("always")
602 else filter_from_string("always")
603 )
603 )
604 ),
604 ),
605 )
605 )
606 )
606 )
607
607
608 # rebuild the bindings list from scratch
608 # rebuild the bindings list from scratch
609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
609 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
610 for binding in shortcuts_to_add:
610 for binding in shortcuts_to_add:
611 add_binding(key_bindings, binding)
611 add_binding(key_bindings, binding)
612
612
613 return key_bindings
613 return key_bindings
614
614
615 prompt_includes_vi_mode = Bool(True,
615 prompt_includes_vi_mode = Bool(True,
616 help="Display the current vi mode (when using vi editing mode)."
616 help="Display the current vi mode (when using vi editing mode)."
617 ).tag(config=True)
617 ).tag(config=True)
618
618
619 prompt_line_number_format = Unicode(
619 prompt_line_number_format = Unicode(
620 "",
620 "",
621 help="The format for line numbering, will be passed `line` (int, 1 based)"
621 help="The format for line numbering, will be passed `line` (int, 1 based)"
622 " the current line number and `rel_line` the relative line number."
622 " the current line number and `rel_line` the relative line number."
623 " for example to display both you can use the following template string :"
623 " for example to display both you can use the following template string :"
624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
624 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
625 " This will display the current line number, with leading space and a width of at least 4"
625 " This will display the current line number, with leading space and a width of at least 4"
626 " character, as well as the relative line number 0 padded and always with a + or - sign."
626 " character, as well as the relative line number 0 padded and always with a + or - sign."
627 " Note that when using Emacs mode the prompt of the first line may not update.",
627 " Note that when using Emacs mode the prompt of the first line may not update.",
628 ).tag(config=True)
628 ).tag(config=True)
629
629
630 @observe('term_title')
630 @observe('term_title')
631 def init_term_title(self, change=None):
631 def init_term_title(self, change=None):
632 # Enable or disable the terminal title.
632 # Enable or disable the terminal title.
633 if self.term_title and _is_tty:
633 if self.term_title and _is_tty:
634 toggle_set_term_title(True)
634 toggle_set_term_title(True)
635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
635 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
636 else:
636 else:
637 toggle_set_term_title(False)
637 toggle_set_term_title(False)
638
638
639 def restore_term_title(self):
639 def restore_term_title(self):
640 if self.term_title and _is_tty:
640 if self.term_title and _is_tty:
641 restore_term_title()
641 restore_term_title()
642
642
643 def init_display_formatter(self):
643 def init_display_formatter(self):
644 super(TerminalInteractiveShell, self).init_display_formatter()
644 super(TerminalInteractiveShell, self).init_display_formatter()
645 # terminal only supports plain text
645 # terminal only supports plain text
646 self.display_formatter.active_types = ["text/plain"]
646 self.display_formatter.active_types = ["text/plain"]
647
647
648 def init_prompt_toolkit_cli(self):
648 def init_prompt_toolkit_cli(self):
649 if self.simple_prompt:
649 if self.simple_prompt:
650 # Fall back to plain non-interactive output for tests.
650 # Fall back to plain non-interactive output for tests.
651 # This is very limited.
651 # This is very limited.
652 def prompt():
652 def prompt():
653 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
653 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
654 lines = [input(prompt_text)]
654 lines = [input(prompt_text)]
655 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
655 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
656 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
657 lines.append( input(prompt_continuation) )
657 lines.append( input(prompt_continuation) )
658 return '\n'.join(lines)
658 return '\n'.join(lines)
659 self.prompt_for_code = prompt
659 self.prompt_for_code = prompt
660 return
660 return
661
661
662 # Set up keyboard shortcuts
662 # Set up keyboard shortcuts
663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
663 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
664
664
665 # Pre-populate history from IPython's history database
665 # Pre-populate history from IPython's history database
666 history = PtkHistoryAdapter(self)
666 history = PtkHistoryAdapter(self)
667
667
668 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
668 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
669 self.style = DynamicStyle(lambda: self._style)
669 self.style = DynamicStyle(lambda: self._style)
670
670
671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
671 editing_mode = getattr(EditingMode, self.editing_mode.upper())
672
672
673 self._use_asyncio_inputhook = False
673 self._use_asyncio_inputhook = False
674 self.pt_app = PromptSession(
674 self.pt_app = PromptSession(
675 auto_suggest=self.auto_suggest,
675 auto_suggest=self.auto_suggest,
676 editing_mode=editing_mode,
676 editing_mode=editing_mode,
677 key_bindings=key_bindings,
677 key_bindings=key_bindings,
678 history=history,
678 history=history,
679 completer=IPythonPTCompleter(shell=self),
679 completer=IPythonPTCompleter(shell=self),
680 enable_history_search=self.enable_history_search,
680 enable_history_search=self.enable_history_search,
681 style=self.style,
681 style=self.style,
682 include_default_pygments_style=False,
682 include_default_pygments_style=False,
683 mouse_support=self.mouse_support,
683 mouse_support=self.mouse_support,
684 enable_open_in_editor=self.extra_open_editor_shortcuts,
684 enable_open_in_editor=self.extra_open_editor_shortcuts,
685 color_depth=self.color_depth,
685 color_depth=self.color_depth,
686 tempfile_suffix=".py",
686 tempfile_suffix=".py",
687 **self._extra_prompt_options(),
687 **self._extra_prompt_options(),
688 )
688 )
689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
689 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
690 self.auto_suggest.connect(self.pt_app)
690 self.auto_suggest.connect(self.pt_app)
691
691
692 def _make_style_from_name_or_cls(self, name_or_cls):
692 def _make_style_from_name_or_cls(self, name_or_cls):
693 """
693 """
694 Small wrapper that make an IPython compatible style from a style name
694 Small wrapper that make an IPython compatible style from a style name
695
695
696 We need that to add style for prompt ... etc.
696 We need that to add style for prompt ... etc.
697 """
697 """
698 style_overrides = {}
698 style_overrides = {}
699 if name_or_cls == 'legacy':
699 if name_or_cls == 'legacy':
700 legacy = self.colors.lower()
700 legacy = self.colors.lower()
701 if legacy == 'linux':
701 if legacy == 'linux':
702 style_cls = get_style_by_name('monokai')
702 style_cls = get_style_by_name('monokai')
703 style_overrides = _style_overrides_linux
703 style_overrides = _style_overrides_linux
704 elif legacy == 'lightbg':
704 elif legacy == 'lightbg':
705 style_overrides = _style_overrides_light_bg
705 style_overrides = _style_overrides_light_bg
706 style_cls = get_style_by_name('pastie')
706 style_cls = get_style_by_name('pastie')
707 elif legacy == 'neutral':
707 elif legacy == 'neutral':
708 # The default theme needs to be visible on both a dark background
708 # The default theme needs to be visible on both a dark background
709 # and a light background, because we can't tell what the terminal
709 # and a light background, because we can't tell what the terminal
710 # looks like. These tweaks to the default theme help with that.
710 # looks like. These tweaks to the default theme help with that.
711 style_cls = get_style_by_name('default')
711 style_cls = get_style_by_name('default')
712 style_overrides.update({
712 style_overrides.update({
713 Token.Number: '#ansigreen',
713 Token.Number: '#ansigreen',
714 Token.Operator: 'noinherit',
714 Token.Operator: 'noinherit',
715 Token.String: '#ansiyellow',
715 Token.String: '#ansiyellow',
716 Token.Name.Function: '#ansiblue',
716 Token.Name.Function: '#ansiblue',
717 Token.Name.Class: 'bold #ansiblue',
717 Token.Name.Class: 'bold #ansiblue',
718 Token.Name.Namespace: 'bold #ansiblue',
718 Token.Name.Namespace: 'bold #ansiblue',
719 Token.Name.Variable.Magic: '#ansiblue',
719 Token.Name.Variable.Magic: '#ansiblue',
720 Token.Prompt: '#ansigreen',
720 Token.Prompt: '#ansigreen',
721 Token.PromptNum: '#ansibrightgreen bold',
721 Token.PromptNum: '#ansibrightgreen bold',
722 Token.OutPrompt: '#ansired',
722 Token.OutPrompt: '#ansired',
723 Token.OutPromptNum: '#ansibrightred bold',
723 Token.OutPromptNum: '#ansibrightred bold',
724 })
724 })
725
725
726 # Hack: Due to limited color support on the Windows console
726 # Hack: Due to limited color support on the Windows console
727 # the prompt colors will be wrong without this
727 # the prompt colors will be wrong without this
728 if os.name == 'nt':
728 if os.name == 'nt':
729 style_overrides.update({
729 style_overrides.update({
730 Token.Prompt: '#ansidarkgreen',
730 Token.Prompt: '#ansidarkgreen',
731 Token.PromptNum: '#ansigreen bold',
731 Token.PromptNum: '#ansigreen bold',
732 Token.OutPrompt: '#ansidarkred',
732 Token.OutPrompt: '#ansidarkred',
733 Token.OutPromptNum: '#ansired bold',
733 Token.OutPromptNum: '#ansired bold',
734 })
734 })
735 elif legacy =='nocolor':
735 elif legacy =='nocolor':
736 style_cls=_NoStyle
736 style_cls=_NoStyle
737 style_overrides = {}
737 style_overrides = {}
738 else :
738 else :
739 raise ValueError('Got unknown colors: ', legacy)
739 raise ValueError('Got unknown colors: ', legacy)
740 else :
740 else :
741 if isinstance(name_or_cls, str):
741 if isinstance(name_or_cls, str):
742 style_cls = get_style_by_name(name_or_cls)
742 style_cls = get_style_by_name(name_or_cls)
743 else:
743 else:
744 style_cls = name_or_cls
744 style_cls = name_or_cls
745 style_overrides = {
745 style_overrides = {
746 Token.Prompt: '#ansigreen',
746 Token.Prompt: '#ansigreen',
747 Token.PromptNum: '#ansibrightgreen bold',
747 Token.PromptNum: '#ansibrightgreen bold',
748 Token.OutPrompt: '#ansired',
748 Token.OutPrompt: '#ansired',
749 Token.OutPromptNum: '#ansibrightred bold',
749 Token.OutPromptNum: '#ansibrightred bold',
750 }
750 }
751 style_overrides.update(self.highlighting_style_overrides)
751 style_overrides.update(self.highlighting_style_overrides)
752 style = merge_styles([
752 style = merge_styles([
753 style_from_pygments_cls(style_cls),
753 style_from_pygments_cls(style_cls),
754 style_from_pygments_dict(style_overrides),
754 style_from_pygments_dict(style_overrides),
755 ])
755 ])
756
756
757 return style
757 return style
758
758
759 @property
759 @property
760 def pt_complete_style(self):
760 def pt_complete_style(self):
761 return {
761 return {
762 'multicolumn': CompleteStyle.MULTI_COLUMN,
762 'multicolumn': CompleteStyle.MULTI_COLUMN,
763 'column': CompleteStyle.COLUMN,
763 'column': CompleteStyle.COLUMN,
764 'readlinelike': CompleteStyle.READLINE_LIKE,
764 'readlinelike': CompleteStyle.READLINE_LIKE,
765 }[self.display_completions]
765 }[self.display_completions]
766
766
767 @property
767 @property
768 def color_depth(self):
768 def color_depth(self):
769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
769 return (ColorDepth.TRUE_COLOR if self.true_color else None)
770
770
771 def _extra_prompt_options(self):
771 def _extra_prompt_options(self):
772 """
772 """
773 Return the current layout option for the current Terminal InteractiveShell
773 Return the current layout option for the current Terminal InteractiveShell
774 """
774 """
775 def get_message():
775 def get_message():
776 return PygmentsTokens(self.prompts.in_prompt_tokens())
776 return PygmentsTokens(self.prompts.in_prompt_tokens())
777
777
778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
778 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
779 # with emacs mode the prompt is (usually) static, so we call only
779 # with emacs mode the prompt is (usually) static, so we call only
780 # the function once. With VI mode it can toggle between [ins] and
780 # the function once. With VI mode it can toggle between [ins] and
781 # [nor] so we can't precompute.
781 # [nor] so we can't precompute.
782 # here I'm going to favor the default keybinding which almost
782 # here I'm going to favor the default keybinding which almost
783 # everybody uses to decrease CPU usage.
783 # everybody uses to decrease CPU usage.
784 # if we have issues with users with custom Prompts we can see how to
784 # if we have issues with users with custom Prompts we can see how to
785 # work around this.
785 # work around this.
786 get_message = get_message()
786 get_message = get_message()
787
787
788 options = {
788 options = {
789 "complete_in_thread": False,
789 "complete_in_thread": False,
790 "lexer": IPythonPTLexer(),
790 "lexer": IPythonPTLexer(),
791 "reserve_space_for_menu": self.space_for_menu,
791 "reserve_space_for_menu": self.space_for_menu,
792 "message": get_message,
792 "message": get_message,
793 "prompt_continuation": (
793 "prompt_continuation": (
794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
794 lambda width, lineno, is_soft_wrap: PygmentsTokens(
795 _backward_compat_continuation_prompt_tokens(
795 _backward_compat_continuation_prompt_tokens(
796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
796 self.prompts.continuation_prompt_tokens, width, lineno=lineno
797 )
797 )
798 )
798 )
799 ),
799 ),
800 "multiline": True,
800 "multiline": True,
801 "complete_style": self.pt_complete_style,
801 "complete_style": self.pt_complete_style,
802 "input_processors": [
802 "input_processors": [
803 # Highlight matching brackets, but only when this setting is
803 # Highlight matching brackets, but only when this setting is
804 # enabled, and only when the DEFAULT_BUFFER has the focus.
804 # enabled, and only when the DEFAULT_BUFFER has the focus.
805 ConditionalProcessor(
805 ConditionalProcessor(
806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
806 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
807 filter=HasFocus(DEFAULT_BUFFER)
807 filter=HasFocus(DEFAULT_BUFFER)
808 & ~IsDone()
808 & ~IsDone()
809 & Condition(lambda: self.highlight_matching_brackets),
809 & Condition(lambda: self.highlight_matching_brackets),
810 ),
810 ),
811 # Show auto-suggestion in lines other than the last line.
811 # Show auto-suggestion in lines other than the last line.
812 ConditionalProcessor(
812 ConditionalProcessor(
813 processor=AppendAutoSuggestionInAnyLine(),
813 processor=AppendAutoSuggestionInAnyLine(),
814 filter=HasFocus(DEFAULT_BUFFER)
814 filter=HasFocus(DEFAULT_BUFFER)
815 & ~IsDone()
815 & ~IsDone()
816 & Condition(
816 & Condition(
817 lambda: isinstance(
817 lambda: isinstance(
818 self.auto_suggest, NavigableAutoSuggestFromHistory
818 self.auto_suggest, NavigableAutoSuggestFromHistory
819 )
819 )
820 ),
820 ),
821 ),
821 ),
822 ],
822 ],
823 }
823 }
824 if not PTK3:
824 if not PTK3:
825 options['inputhook'] = self.inputhook
825 options['inputhook'] = self.inputhook
826
826
827 return options
827 return options
828
828
829 def prompt_for_code(self):
829 def prompt_for_code(self):
830 if self.rl_next_input:
830 if self.rl_next_input:
831 default = self.rl_next_input
831 default = self.rl_next_input
832 self.rl_next_input = None
832 self.rl_next_input = None
833 else:
833 else:
834 default = ''
834 default = ''
835
835
836 # In order to make sure that asyncio code written in the
836 # In order to make sure that asyncio code written in the
837 # interactive shell doesn't interfere with the prompt, we run the
837 # interactive shell doesn't interfere with the prompt, we run the
838 # prompt in a different event loop.
838 # prompt in a different event loop.
839 # If we don't do this, people could spawn coroutine with a
839 # If we don't do this, people could spawn coroutine with a
840 # while/true inside which will freeze the prompt.
840 # while/true inside which will freeze the prompt.
841
841
842 with patch_stdout(raw=True):
842 with patch_stdout(raw=True):
843 if self._use_asyncio_inputhook:
843 if self._use_asyncio_inputhook:
844 # When we integrate the asyncio event loop, run the UI in the
844 # When we integrate the asyncio event loop, run the UI in the
845 # same event loop as the rest of the code. don't use an actual
845 # same event loop as the rest of the code. don't use an actual
846 # input hook. (Asyncio is not made for nesting event loops.)
846 # input hook. (Asyncio is not made for nesting event loops.)
847 asyncio_loop = get_asyncio_loop()
847 asyncio_loop = get_asyncio_loop()
848 text = asyncio_loop.run_until_complete(
848 text = asyncio_loop.run_until_complete(
849 self.pt_app.prompt_async(
849 self.pt_app.prompt_async(
850 default=default, **self._extra_prompt_options()
850 default=default, **self._extra_prompt_options()
851 )
851 )
852 )
852 )
853 else:
853 else:
854 text = self.pt_app.prompt(
854 text = self.pt_app.prompt(
855 default=default,
855 default=default,
856 inputhook=self._inputhook,
856 inputhook=self._inputhook,
857 **self._extra_prompt_options(),
857 **self._extra_prompt_options(),
858 )
858 )
859
859
860 return text
860 return text
861
861
862 def enable_win_unicode_console(self):
863 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
864 # console by default, so WUC shouldn't be needed.
865 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
866 DeprecationWarning,
867 stacklevel=2)
868
869 def init_io(self):
862 def init_io(self):
870 if sys.platform not in {'win32', 'cli'}:
863 if sys.platform not in {'win32', 'cli'}:
871 return
864 return
872
865
873 import colorama
866 import colorama
874 colorama.init()
867 colorama.init()
875
868
876 def init_magics(self):
869 def init_magics(self):
877 super(TerminalInteractiveShell, self).init_magics()
870 super(TerminalInteractiveShell, self).init_magics()
878 self.register_magics(TerminalMagics)
871 self.register_magics(TerminalMagics)
879
872
880 def init_alias(self):
873 def init_alias(self):
881 # The parent class defines aliases that can be safely used with any
874 # The parent class defines aliases that can be safely used with any
882 # frontend.
875 # frontend.
883 super(TerminalInteractiveShell, self).init_alias()
876 super(TerminalInteractiveShell, self).init_alias()
884
877
885 # Now define aliases that only make sense on the terminal, because they
878 # Now define aliases that only make sense on the terminal, because they
886 # need direct access to the console in a way that we can't emulate in
879 # need direct access to the console in a way that we can't emulate in
887 # GUI or web frontend
880 # GUI or web frontend
888 if os.name == 'posix':
881 if os.name == 'posix':
889 for cmd in ('clear', 'more', 'less', 'man'):
882 for cmd in ('clear', 'more', 'less', 'man'):
890 self.alias_manager.soft_define_alias(cmd, cmd)
883 self.alias_manager.soft_define_alias(cmd, cmd)
891
884
892 def __init__(self, *args, **kwargs) -> None:
885 def __init__(self, *args, **kwargs) -> None:
893 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
886 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
894 self._set_autosuggestions(self.autosuggestions_provider)
887 self._set_autosuggestions(self.autosuggestions_provider)
895 self.init_prompt_toolkit_cli()
888 self.init_prompt_toolkit_cli()
896 self.init_term_title()
889 self.init_term_title()
897 self.keep_running = True
890 self.keep_running = True
898 self._set_formatter(self.autoformatter)
891 self._set_formatter(self.autoformatter)
899
892
900 def ask_exit(self):
893 def ask_exit(self):
901 self.keep_running = False
894 self.keep_running = False
902
895
903 rl_next_input = None
896 rl_next_input = None
904
897
905 def interact(self):
898 def interact(self):
906 self.keep_running = True
899 self.keep_running = True
907 while self.keep_running:
900 while self.keep_running:
908 print(self.separate_in, end='')
901 print(self.separate_in, end='')
909
902
910 try:
903 try:
911 code = self.prompt_for_code()
904 code = self.prompt_for_code()
912 except EOFError:
905 except EOFError:
913 if (not self.confirm_exit) \
906 if (not self.confirm_exit) \
914 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
907 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
915 self.ask_exit()
908 self.ask_exit()
916
909
917 else:
910 else:
918 if code:
911 if code:
919 self.run_cell(code, store_history=True)
912 self.run_cell(code, store_history=True)
920
913
921 def mainloop(self):
914 def mainloop(self):
922 # An extra layer of protection in case someone mashing Ctrl-C breaks
915 # An extra layer of protection in case someone mashing Ctrl-C breaks
923 # out of our internal code.
916 # out of our internal code.
924 while True:
917 while True:
925 try:
918 try:
926 self.interact()
919 self.interact()
927 break
920 break
928 except KeyboardInterrupt as e:
921 except KeyboardInterrupt as e:
929 print("\n%s escaped interact()\n" % type(e).__name__)
922 print("\n%s escaped interact()\n" % type(e).__name__)
930 finally:
923 finally:
931 # An interrupt during the eventloop will mess up the
924 # An interrupt during the eventloop will mess up the
932 # internal state of the prompt_toolkit library.
925 # internal state of the prompt_toolkit library.
933 # Stopping the eventloop fixes this, see
926 # Stopping the eventloop fixes this, see
934 # https://github.com/ipython/ipython/pull/9867
927 # https://github.com/ipython/ipython/pull/9867
935 if hasattr(self, '_eventloop'):
928 if hasattr(self, '_eventloop'):
936 self._eventloop.stop()
929 self._eventloop.stop()
937
930
938 self.restore_term_title()
931 self.restore_term_title()
939
932
940 # try to call some at-exit operation optimistically as some things can't
933 # try to call some at-exit operation optimistically as some things can't
941 # be done during interpreter shutdown. this is technically inaccurate as
934 # be done during interpreter shutdown. this is technically inaccurate as
942 # this make mainlool not re-callable, but that should be a rare if not
935 # this make mainlool not re-callable, but that should be a rare if not
943 # in existent use case.
936 # in existent use case.
944
937
945 self._atexit_once()
938 self._atexit_once()
946
939
947 _inputhook = None
940 _inputhook = None
948 def inputhook(self, context):
941 def inputhook(self, context):
949 if self._inputhook is not None:
942 if self._inputhook is not None:
950 self._inputhook(context)
943 self._inputhook(context)
951
944
952 active_eventloop: Optional[str] = None
945 active_eventloop: Optional[str] = None
953
946
954 def enable_gui(self, gui: Optional[str] = None) -> None:
947 def enable_gui(self, gui: Optional[str] = None) -> None:
955 if gui:
948 if gui:
956 from ..core.pylabtools import _convert_gui_from_matplotlib
949 from ..core.pylabtools import _convert_gui_from_matplotlib
957
950
958 gui = _convert_gui_from_matplotlib(gui)
951 gui = _convert_gui_from_matplotlib(gui)
959
952
960 if self.simple_prompt is True and gui is not None:
953 if self.simple_prompt is True and gui is not None:
961 print(
954 print(
962 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
955 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
963 )
956 )
964 print(
957 print(
965 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
958 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
966 )
959 )
967 return
960 return
968
961
969 if self._inputhook is None and gui is None:
962 if self._inputhook is None and gui is None:
970 print("No event loop hook running.")
963 print("No event loop hook running.")
971 return
964 return
972
965
973 if self._inputhook is not None and gui is not None:
966 if self._inputhook is not None and gui is not None:
974 newev, newinhook = get_inputhook_name_and_func(gui)
967 newev, newinhook = get_inputhook_name_and_func(gui)
975 if self._inputhook == newinhook:
968 if self._inputhook == newinhook:
976 # same inputhook, do nothing
969 # same inputhook, do nothing
977 self.log.info(
970 self.log.info(
978 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
971 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
979 )
972 )
980 return
973 return
981 self.log.warning(
974 self.log.warning(
982 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
975 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
983 "Call with no arguments to disable the current loop."
976 "Call with no arguments to disable the current loop."
984 )
977 )
985 return
978 return
986 if self._inputhook is not None and gui is None:
979 if self._inputhook is not None and gui is None:
987 self.active_eventloop = self._inputhook = None
980 self.active_eventloop = self._inputhook = None
988
981
989 if gui and (gui not in {None, "webagg"}):
982 if gui and (gui not in {None, "webagg"}):
990 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
983 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
991 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
984 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
992 else:
985 else:
993 self.active_eventloop = self._inputhook = None
986 self.active_eventloop = self._inputhook = None
994
987
995 self._use_asyncio_inputhook = gui == "asyncio"
988 self._use_asyncio_inputhook = gui == "asyncio"
996
989
997 # Run !system commands directly, not through pipes, so terminal programs
990 # Run !system commands directly, not through pipes, so terminal programs
998 # work correctly.
991 # work correctly.
999 system = InteractiveShell.system_raw
992 system = InteractiveShell.system_raw
1000
993
1001 def auto_rewrite_input(self, cmd):
994 def auto_rewrite_input(self, cmd):
1002 """Overridden from the parent class to use fancy rewriting prompt"""
995 """Overridden from the parent class to use fancy rewriting prompt"""
1003 if not self.show_rewritten_input:
996 if not self.show_rewritten_input:
1004 return
997 return
1005
998
1006 tokens = self.prompts.rewrite_prompt_tokens()
999 tokens = self.prompts.rewrite_prompt_tokens()
1007 if self.pt_app:
1000 if self.pt_app:
1008 print_formatted_text(PygmentsTokens(tokens), end='',
1001 print_formatted_text(PygmentsTokens(tokens), end='',
1009 style=self.pt_app.app.style)
1002 style=self.pt_app.app.style)
1010 print(cmd)
1003 print(cmd)
1011 else:
1004 else:
1012 prompt = ''.join(s for t, s in tokens)
1005 prompt = ''.join(s for t, s in tokens)
1013 print(prompt, cmd, sep='')
1006 print(prompt, cmd, sep='')
1014
1007
1015 _prompts_before = None
1008 _prompts_before = None
1016 def switch_doctest_mode(self, mode):
1009 def switch_doctest_mode(self, mode):
1017 """Switch prompts to classic for %doctest_mode"""
1010 """Switch prompts to classic for %doctest_mode"""
1018 if mode:
1011 if mode:
1019 self._prompts_before = self.prompts
1012 self._prompts_before = self.prompts
1020 self.prompts = ClassicPrompts(self)
1013 self.prompts = ClassicPrompts(self)
1021 elif self._prompts_before:
1014 elif self._prompts_before:
1022 self.prompts = self._prompts_before
1015 self.prompts = self._prompts_before
1023 self._prompts_before = None
1016 self._prompts_before = None
1024 # self._update_layout()
1017 # self._update_layout()
1025
1018
1026
1019
1027 InteractiveShellABC.register(TerminalInteractiveShell)
1020 InteractiveShellABC.register(TerminalInteractiveShell)
1028
1021
1029 if __name__ == '__main__':
1022 if __name__ == '__main__':
1030 TerminalInteractiveShell.instance().interact()
1023 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now