https://github.com/python/cpython/commit/29d9453cc46c2c929aa458ff1d94a71ba3015113
commit: 29d9453cc46c2c929aa458ff1d94a71ba3015113
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-09-16T22:44:41-04:00
summary:

[3.14] gh-105689: Parse only the current statement in the IDLE Shell 
(GH-157594) (#157651)

gh-105689: Parse only the current statement in the IDLE Shell (GH-157594)

Since the prompts moved to the sidebar, sys.ps1 ends with a newline
and prompt_last_line is empty, so HyperParser and newline_and_indent
took the editor path in the Shell and parsed previous output. Use an
explicit is_shell attribute instead.
(cherry picked from commit b42dcf6d58cbb0e43c0497e6fa14407996c96848)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/IDLE/2026-09-15-21-54-04.gh-issue-105689.ASoU37.rst
M Lib/idlelib/editor.py
M Lib/idlelib/hyperparser.py
M Lib/idlelib/idle_test/test_autocomplete.py
M Lib/idlelib/idle_test/test_calltip.py
M Lib/idlelib/idle_test/test_hyperparser.py
M Lib/idlelib/idle_test/test_parenmatch.py
M Lib/idlelib/pyshell.py

diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
index 5e9f6aa86e81925..8e15319b5baab20 100644
--- a/Lib/idlelib/editor.py
+++ b/Lib/idlelib/editor.py
@@ -35,6 +35,7 @@
 darwin = sys.platform == 'darwin'
 
 class EditorWindow:
+    is_shell = False  # PyShell overrides.
     from idlelib.percolator import Percolator
     from idlelib.colorizer import ColorDelegator, color_config
     from idlelib.undo import UndoDelegator
@@ -80,7 +81,6 @@ def __init__(self, flist=None, filename=None, key=None, 
root=None):
         self.recent_files_path = idleConf.userdir and os.path.join(
                 idleConf.userdir, 'recent-files.lst')
 
-        self.prompt_last_line = ''  # Override in PyShell
         self.text_frame = text_frame = Frame(top)
         self.vbar = vbar = Scrollbar(text_frame, name='vbar')
         width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
@@ -1434,7 +1434,7 @@ def newline_and_indent_event(self, event):
             # First need to find the last statement.
             lno = index2line(text.index('insert'))
             y = pyparse.Parser(self.indentwidth, self.tabwidth)
-            if not self.prompt_last_line:
+            if not self.is_shell:
                 for context in self.num_context_lines:
                     startat = max(lno - context, 1)
                     startatindex = repr(startat) + ".0"
diff --git a/Lib/idlelib/hyperparser.py b/Lib/idlelib/hyperparser.py
index 76144ee8fb30f5e..d1a28b274a499ff 100644
--- a/Lib/idlelib/hyperparser.py
+++ b/Lib/idlelib/hyperparser.py
@@ -35,7 +35,7 @@ def index2line(index):
             return int(float(index))
         lno = index2line(text.index(index))
 
-        if not editwin.prompt_last_line:
+        if not editwin.is_shell:
             for context in editwin.num_context_lines:
                 startat = max(lno - context, 1)
                 startatindex = repr(startat) + ".0"
diff --git a/Lib/idlelib/idle_test/test_autocomplete.py 
b/Lib/idlelib/idle_test/test_autocomplete.py
index 9086c31d2733b61..cd4664d21722bb5 100644
--- a/Lib/idlelib/idle_test/test_autocomplete.py
+++ b/Lib/idlelib/idle_test/test_autocomplete.py
@@ -19,7 +19,7 @@ def __init__(self, root, text):
         self.text = text
         self.indentwidth = 8
         self.tabwidth = 8
-        self.prompt_last_line = '>>>'  # Currently not used by autocomplete.
+        self.is_shell = True
 
 
 class AutoCompleteTest(unittest.TestCase):
diff --git a/Lib/idlelib/idle_test/test_calltip.py 
b/Lib/idlelib/idle_test/test_calltip.py
index 28c196a42672fcd..e216ae67f68b4d1 100644
--- a/Lib/idlelib/idle_test/test_calltip.py
+++ b/Lib/idlelib/idle_test/test_calltip.py
@@ -282,7 +282,7 @@ class mock_Shell:
     def __init__(self, text):
         text.tag_prevrange = Mock(return_value=None)
         self.text = text
-        self.prompt_last_line = ">>> "
+        self.is_shell = True
         self.indentwidth = 4
         self.tabwidth = 8
 
diff --git a/Lib/idlelib/idle_test/test_hyperparser.py 
b/Lib/idlelib/idle_test/test_hyperparser.py
index 343843c4166e97d..df61d2641d3c7f2 100644
--- a/Lib/idlelib/idle_test/test_hyperparser.py
+++ b/Lib/idlelib/idle_test/test_hyperparser.py
@@ -11,7 +11,7 @@ def __init__(self, text):
         self.text = text
         self.indentwidth = 8
         self.tabwidth = 8
-        self.prompt_last_line = '>>>'
+        self.is_shell = True
         self.num_context_lines = 50, 500, 1000
 
     _build_char_in_string_func = EditorWindow._build_char_in_string_func
@@ -53,7 +53,7 @@ def setUp(self):
 
     def tearDown(self):
         self.text.delete('1.0', 'end')
-        self.editwin.prompt_last_line = '>>>'
+        self.editwin.is_shell = True
 
     def get_parser(self, index):
         """
@@ -70,8 +70,8 @@ def test_init(self):
             p = self.get_parser('1.5')
         self.assertIn('precedes', str(ve.exception))
 
-        # test without ps1
-        self.editwin.prompt_last_line = ''
+        # test an editor
+        self.editwin.is_shell = False
 
         # number of lines lesser than 50
         p = self.get_parser('end')
diff --git a/Lib/idlelib/idle_test/test_parenmatch.py 
b/Lib/idlelib/idle_test/test_parenmatch.py
index 2e10d7cd36760ff..4907d77fff4c7c6 100644
--- a/Lib/idlelib/idle_test/test_parenmatch.py
+++ b/Lib/idlelib/idle_test/test_parenmatch.py
@@ -17,7 +17,7 @@ def __init__(self, text):
         self.text = text
         self.indentwidth = 8
         self.tabwidth = 8
-        self.prompt_last_line = '>>>' # Currently not used by parenmatch.
+        self.is_shell = True
 
 
 class ParenMatchTest(unittest.TestCase):
diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py
index b69a5980bc5338b..997b91e53327d7b 100755
--- a/Lib/idlelib/pyshell.py
+++ b/Lib/idlelib/pyshell.py
@@ -852,6 +852,7 @@ def display_executing_dialog(self):
 
 
 class PyShell(OutputWindow):
+    is_shell = True
     from idlelib.squeezer import Squeezer
 
     shell_title = "IDLE Shell"
@@ -909,7 +910,6 @@ def __init__(self, flist=None):
         self.indentwidth = 4
 
         self.sys_ps1 = sys.ps1 if hasattr(sys, 'ps1') else '>>>\n'
-        self.prompt_last_line = self.sys_ps1.split('\n')[-1]
         self.prompt = self.sys_ps1  # Changes when debug active
 
         text = self.text
diff --git 
a/Misc/NEWS.d/next/IDLE/2026-09-15-21-54-04.gh-issue-105689.ASoU37.rst 
b/Misc/NEWS.d/next/IDLE/2026-09-15-21-54-04.gh-issue-105689.ASoU37.rst
new file mode 100644
index 000000000000000..7b4924fa0af2283
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2026-09-15-21-54-04.gh-issue-105689.ASoU37.rst
@@ -0,0 +1,2 @@
+Fix calltips, parenthesis matching and auto-indent in the IDLE Shell after
+output containing unbalanced quotes or parentheses.

_______________________________________________
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