https://github.com/python/cpython/commit/b67a79d716a5c4fd6c83eab38e93af27df2b3bf5
commit: b67a79d716a5c4fd6c83eab38e93af27df2b3bf5
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-09-21T11:53:52+03:00
summary:

[3.15] gh-75512: Keep the prompt with the input in progress when a thread 
prints in the IDLE Shell (GH-157706) (#157835)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/IDLE/2026-09-17-22-00-00.gh-issue-75512.asyncout.rst
M Lib/idlelib/idle_test/test_pyshell.py
M Lib/idlelib/pyshell.py

diff --git a/Lib/idlelib/idle_test/test_pyshell.py 
b/Lib/idlelib/idle_test/test_pyshell.py
index dec81bdbbccd67e..0e292e7be87ee48 100644
--- a/Lib/idlelib/idle_test/test_pyshell.py
+++ b/Lib/idlelib/idle_test/test_pyshell.py
@@ -69,6 +69,46 @@ def test_init(self):
 ##        self.assertIsInstance(ps, pyshell.PyShell)
 
 
+class PyShellTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        requires('gui')
+        cls.root = Tk()
+        cls.root.withdraw()
+        cls.shell = pyshell.PyShell(pyshell.PyShellFileList(cls.root))
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.shell.close()
+        del cls.shell
+        cls.root.destroy()
+        del cls.root
+
+    def setUp(self):
+        text = self.shell.text
+        self.shell.per.bottom.delete('1.0', 'end')
+        text.mark_set('iomark', '1.0')  # As after begin().
+        text.mark_gravity('iomark', 'left')
+        self.shell.undo.reset_undo()
+
+    def test_output_at_prompt(self):
+        # gh-75512: output from a thread while a prompt is shown goes
+        # before the prompt, which stays with the input in progress.
+        shell = self.shell
+        text = shell.text
+        shell.write('x\n', 'stdout')
+        shell.resetoutput()
+        text.tag_add('console', 'iomark-1c')  # As showprompt() does.
+        text.insert('end-1c', 'a = (')
+        shell.write('hel', 'stdout')
+        shell.write('lo\n', 'stdout')
+        self.assertEqual(text.get('iomark-6c', 'end-1c'), 'hello\na = (')
+        self.assertIn('console', text.tag_names('iomark-1c'))
+        self.assertNotIn('console', text.tag_names('iomark-7c'))
+        self.assertEqual(shell.shell_sidebar.line_prompts, {3: '>>>'})
+
+
 class PyShellRemoveLastNewlineAndSurroundingWhitespaceTest(unittest.TestCase):
     regexp = pyshell.PyShell._last_newline_re
 
diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py
index a93b5cca7e1c834..5eb90e063e19583 100755
--- a/Lib/idlelib/pyshell.py
+++ b/Lib/idlelib/pyshell.py
@@ -1432,13 +1432,24 @@ def resetoutput(self):
         self.ctip.remove_calltip_window()
 
     def write(self, s, tags=()):
+        text = self.text
+        # Move the prompt (the "console" tag on the preceding newline)
+        # after output which comes while it is shown (gh-75512).
+        at_prompt = (s and tags in ("stdout", "stderr")
+                     and not self.executing and not self.reading)
+        if at_prompt:
+            text.tag_remove("console", "iomark-1c")
+            text.tag_remove("stdin", "iomark-1c")
         try:
-            self.text.mark_gravity("iomark", "right")
+            text.mark_gravity("iomark", "right")
             count = OutputWindow.write(self, s, tags, "iomark")
-            self.text.mark_gravity("iomark", "left")
+            text.mark_gravity("iomark", "left")
         except:
             raise ###pass  # ### 11Aug07 KBK if we are expecting exceptions
                            # let's find out what they are and be specific.
+        if at_prompt and s.endswith('\n'):
+            text.tag_add("console", "iomark-1c")
+            self.shell_sidebar.update_sidebar()
         if self.canceled:
             self.canceled = False
             if not use_subprocess:
diff --git 
a/Misc/NEWS.d/next/IDLE/2026-09-17-22-00-00.gh-issue-75512.asyncout.rst 
b/Misc/NEWS.d/next/IDLE/2026-09-17-22-00-00.gh-issue-75512.asyncout.rst
new file mode 100644
index 000000000000000..166cb6971f9b0e2
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2026-09-17-22-00-00.gh-issue-75512.asyncout.rst
@@ -0,0 +1,2 @@
+Output from a thread printed in the IDLE Shell while a statement is being
+entered no longer takes over the prompt of the statement.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to