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