From 1538b26b461f8aef9d404ecbe783a67983d7eb5b Mon Sep 17 00:00:00 2001 From: Maxim Popov Date: Wed, 8 Jul 2026 19:28:28 +0000 Subject: [PATCH] PY-90741 [debugger]: modify redirection parameter in pydevd_io The issue with duplicated output in REPL is solved if we remove the output stream duplication during stream redirection in pydevd_io. All REPL messages will now be routed through JSON-RPC and not kept forwarded through stdout. This path is only used by REPL, so other messages (regular output, etc.) are not affected and may go either through JSON-RPC or through stdout Issue Explanation: In integratedTerminal mode, two independent paths deliver stdout to the IDE during Debug Console (REPL) evaluation: 1. forwardStream in PythonDapClient reads the process stdout pipe and sends DAP output events. 2. pydevd temporarily redirects sys.stdout via IORedirector during REPL evaluation (when redirectOutput is false). IORedirector.write() writes to both the original pipe and RedirectToPyDBIoMessages, which sends a second DAP output event through JSON-RPC. Both events flow through the same output channel, causing print() output to appear twice. Also fix a secondary bug in formatAndPrintOutput where IMPORTANT-category events were printed twice (once as SYSTEM, once as STDERR) due to a missing return statement. Test: add testDebugConsolePrintNotDuplicated to PyDebugConsoleTest that asserts print() output appears exactly once. Merge-request: IJ-MR-212126 Merged-by: Maxim Popov (cherry picked from commit e1965de20e80c118e16872116fb14f45dc82b83b) IJ-MR-212126 GitOrigin-RevId: 53228ff3a861e293e2095a81b37ed3e36262a12e --- .../_vendored/pydevd/_pydevd_bundle/pydevd_io.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/python/helpers/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_io.py b/python/helpers/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_io.py index 7d3bffb807a2..b04c208ecbca 100644 --- a/python/helpers/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_io.py +++ b/python/helpers/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_io.py @@ -201,10 +201,14 @@ def end_redirect(std="stdout"): setattr(sys, std, redirect_info.original) -def redirect_stream_to_pydb_io_messages(std): +def redirect_stream_to_pydb_io_messages(std, keep_original_redirection=True): """ :param std: 'stdout' or 'stderr' + :param keep_original_redirection: + If True (default), wraps sys.stdout/stderr with IORedirector that fans out to both the + original stream and RedirectToPyDBIoMessages. If False, replaces sys.stdout/stderr entirely + with RedirectToPyDBIoMessages (output goes only through JSON-RPC). """ with _RedirectionsHolder._lock: redirect_to_name = "_pydevd_%s_redirect_" % (std,) @@ -213,7 +217,7 @@ def redirect_stream_to_pydb_io_messages(std): original = getattr(sys, std) redirect_to = RedirectToPyDBIoMessages(1 if std == "stdout" else 2, original, wrap_buffer) - start_redirect(keep_original_redirection=True, std=std, redirect_to=redirect_to) + start_redirect(keep_original_redirection=keep_original_redirection, std=std, redirect_to=redirect_to) stack = getattr(_RedirectionsHolder, "_stack_%s" % std) setattr(_RedirectionsHolder, redirect_to_name, stack[-1]) @@ -246,7 +250,7 @@ def redirect_stream_to_pydb_io_messages_context(): with _RedirectionsHolder._lock: redirecting = [] for std in ("stdout", "stderr"): - if redirect_stream_to_pydb_io_messages(std): + if redirect_stream_to_pydb_io_messages(std, keep_original_redirection=False): redirecting.append(std) try: