@@ -318,12 +318,34 @@ def namespace(self):
318318
319319
320320class _PdbInteractiveConsole (code .InteractiveConsole ):
321- def __init__ (self , ns , message ):
321+ def __init__ (self , ns = None , message = None ):
322322 self ._message = message
323323 super ().__init__ (locals = ns , local_exit = True )
324324
325325 def write (self , data ):
326- self ._message (data , end = '' )
326+ if self ._message is not None :
327+ self ._message (data , end = '' )
328+ else :
329+ super ().write (data )
330+
331+ def more_lines (self , text ):
332+ # Generic Python multi-line completeness heuristic.
333+ # Strips pyrepl's trailing auto-indent before compiling.
334+ # This should be functionally identical to simple_interact._more_lines
335+ src = text .rstrip (" \t " )
336+ n = len (src )
337+ if n > 0 and text [n - 1 ] == '\n ' :
338+ text = src
339+ try :
340+ code_obj = self .compile (text , "<stdin>" , "single" )
341+ except (OverflowError , SyntaxError , ValueError ):
342+ lines = text .splitlines (keepends = True )
343+ if len (lines ) == 1 :
344+ return False
345+ last = lines [- 1 ]
346+ return ((last .startswith ((" " , "\t " )) or last .strip () != "" )
347+ and not last .endswith ("\n " ))
348+ return code_obj is None
327349
328350
329351# Interaction prompt line will separate file and call info from code
@@ -352,6 +374,96 @@ def get_default_backend():
352374 return _default_backend
353375
354376
377+ def _pyrepl_available ():
378+ """return whether pdb should use _pyrepl for input"""
379+ if not os .getenv ("PYTHON_BASIC_REPL" ):
380+ CAN_USE_PYREPL = False
381+ else :
382+ try :
383+ from _pyrepl .main import CAN_USE_PYREPL
384+ except ModuleNotFoundError :
385+ CAN_USE_PYREPL = False
386+ return CAN_USE_PYREPL
387+
388+
389+ class PdbPyReplInput :
390+ def __init__ (self , pdb_instance , stdin , stdout , prompt ):
391+ import _pyrepl .readline
392+
393+ self .pdb_instance = pdb_instance
394+ self .prompt = prompt
395+ self .console = _PdbInteractiveConsole ()
396+ if not (os .isatty (stdin .fileno ())):
397+ raise ValueError ("stdin is not a TTY" )
398+ self .readline_wrapper = _pyrepl .readline ._ReadlineWrapper (
399+ f_in = stdin .fileno (),
400+ f_out = stdout .fileno (),
401+ config = _pyrepl .readline .ReadlineConfig (
402+ completer_delims = frozenset (' \t \n `@#%^&*()=+[{]}\\ |;:\' ",<>?' )
403+ )
404+ )
405+
406+ def readline (self ):
407+
408+ def more_lines (text ):
409+ if text .strip () == "\x1a " :
410+ # Ctrl + Z raises EOFError to quit pdb
411+ # This is similarly handled in simple_interact.py
412+ raise EOFError
413+ cmd , _ , line = self .pdb_instance .parseline (text )
414+ if not line or not cmd :
415+ return False
416+ func = getattr (self .pdb_instance , 'do_' + cmd , None )
417+ if func is not None :
418+ return False
419+ return self .console .more_lines (text )
420+
421+ try :
422+ pyrepl_completer = self .readline_wrapper .get_completer ()
423+ self .readline_wrapper .set_completer (self .complete )
424+ multiline = (
425+ self .readline_wrapper .multiline_input (
426+ more_lines ,
427+ self .prompt ,
428+ '... ' + ' ' * (len (self .prompt ) - 4 )
429+ ) + '\n '
430+ )
431+ return multiline
432+ except EOFError :
433+ return 'EOF'
434+ finally :
435+ self .readline_wrapper .set_completer (pyrepl_completer )
436+
437+ def complete (self , text , state ):
438+ """
439+ This function is very similar to cmd.Cmd.complete.
440+ However, cmd.Cmd.complete assumes that we use readline module, but
441+ pyrepl does not use it.
442+ """
443+ if state == 0 :
444+ origline = self .readline_wrapper .get_line_buffer ()
445+ line = origline .lstrip ()
446+ stripped = len (origline ) - len (line )
447+ begidx = self .readline_wrapper .get_begidx () - stripped
448+ endidx = self .readline_wrapper .get_endidx () - stripped
449+ if begidx > 0 :
450+ cmd , args , foo = self .pdb_instance .parseline (line )
451+ if not cmd :
452+ compfunc = self .pdb_instance .completedefault
453+ else :
454+ try :
455+ compfunc = getattr (self .pdb_instance , 'complete_' + cmd )
456+ except AttributeError :
457+ compfunc = self .pdb_instance .completedefault
458+ else :
459+ compfunc = self .pdb_instance .completenames
460+ self .completion_matches = compfunc (text , line , begidx , endidx )
461+ try :
462+ return self .completion_matches [state ]
463+ except IndexError :
464+ return None
465+
466+
355467class Pdb (bdb .Bdb , cmd .Cmd ):
356468 _previous_sigint_handler = None
357469
@@ -386,6 +498,12 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
386498 except ImportError :
387499 pass
388500
501+ self .pyrepl_input = None
502+ if _pyrepl_available ():
503+ try :
504+ self .pyrepl_input = PdbPyReplInput (self , self .stdin , self .stdout , self .prompt )
505+ except Exception :
506+ pass
389507 self .allow_kbdint = False
390508 self .nosigint = nosigint
391509 # Consider these characters as part of the command so when the users type
@@ -624,14 +742,40 @@ def user_exception(self, frame, exc_info):
624742 self .message ('%s%s' % (prefix , self ._format_exc (exc_value )))
625743 self .interaction (frame , exc_traceback )
626744
745+ @contextmanager
746+ def _replace_attribute (self , attrs ):
747+ original_attrs = {}
748+ for attr , value in attrs .items ():
749+ original_attrs [attr ] = getattr (self , attr )
750+ setattr (self , attr , value )
751+ try :
752+ yield
753+ finally :
754+ for attr , value in original_attrs .items ():
755+ setattr (self , attr , value )
756+
757+ @contextmanager
758+ def _maybe_use_pyrepl_as_stdin (self ):
759+ if self .pyrepl_input is None :
760+ yield
761+ return
762+
763+ with self ._replace_attribute ({
764+ 'stdin' : self .pyrepl_input ,
765+ 'use_rawinput' : False ,
766+ 'prompt' : '' ,
767+ }):
768+ yield
769+
627770 # General interaction function
628771 def _cmdloop (self ):
629772 while True :
630773 try :
631774 # keyboard interrupts allow for an easy way to cancel
632775 # the current command, so allow them during interactive input
633776 self .allow_kbdint = True
634- self .cmdloop ()
777+ with self ._maybe_use_pyrepl_as_stdin ():
778+ self .cmdloop ()
635779 self .allow_kbdint = False
636780 break
637781 except KeyboardInterrupt :
@@ -2364,10 +2508,21 @@ def do_interact(self, arg):
23642508 contains all the (global and local) names found in the current scope.
23652509 """
23662510 ns = {** self .curframe .f_globals , ** self .curframe .f_locals }
2367- with self ._enable_rlcompleter (ns ):
2368- console = _PdbInteractiveConsole (ns , message = self .message )
2369- console .interact (banner = "*pdb interact start*" ,
2370- exitmsg = "*exit from pdb interact command*" )
2511+ console = _PdbInteractiveConsole (ns , message = self .message )
2512+ banner = "*pdb interact start*"
2513+ exitmsg = "*exit from pdb interact command*"
2514+ if self .pyrepl_input is not None :
2515+ from _pyrepl .simple_interact import run_multiline_interactive_console
2516+ self .message (banner )
2517+ try :
2518+ run_multiline_interactive_console (console )
2519+ except SystemExit :
2520+ pass
2521+ self .message (exitmsg )
2522+ else :
2523+ with self ._enable_rlcompleter (ns ):
2524+ console .interact (banner = banner ,
2525+ exitmsg = exitmsg )
23712526
23722527 def do_alias (self , arg ):
23732528 """alias [name [command]]
0 commit comments