mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	[3.13] gh-121610: pyrepl - handle extending blocks when multi-statement blocks are pasted (GH-121757) (GH-121825)
console.compile with the "single" param throws an exception when
there are multiple statements, never allowing to adding newlines
to a pasted code block (gh-121610)
This adds a few extra checks to allow extending when in an indented
block, and tests for a few examples.
(cherry picked from commit 7d111dac16)
Co-authored-by: saucoide <32314353+saucoide@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
			
			
This commit is contained in:
		
							parent
							
								
									a1505afd39
								
							
						
					
					
						commit
						73f77e642a
					
				
					 2 changed files with 123 additions and 11 deletions
				
			
		|  | @ -27,6 +27,7 @@ | ||||||
| 
 | 
 | ||||||
| import _sitebuiltins | import _sitebuiltins | ||||||
| import linecache | import linecache | ||||||
|  | import functools | ||||||
| import sys | import sys | ||||||
| import code | import code | ||||||
| 
 | 
 | ||||||
|  | @ -78,6 +79,25 @@ def _clear_screen(): | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool: | ||||||
|  |     # ooh, look at the hack: | ||||||
|  |     src = _strip_final_indent(unicodetext) | ||||||
|  |     try: | ||||||
|  |         code = console.compile(src, "<stdin>", "single") | ||||||
|  |     except (OverflowError, SyntaxError, ValueError): | ||||||
|  |         lines = src.splitlines(keepends=True) | ||||||
|  |         if len(lines) == 1: | ||||||
|  |             return False | ||||||
|  | 
 | ||||||
|  |         last_line = lines[-1] | ||||||
|  |         was_indented = last_line.startswith((" ", "\t")) | ||||||
|  |         not_empty = last_line.strip() != "" | ||||||
|  |         incomplete = not last_line.endswith("\n") | ||||||
|  |         return (was_indented or not_empty) and incomplete | ||||||
|  |     else: | ||||||
|  |         return code is None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def run_multiline_interactive_console( | def run_multiline_interactive_console( | ||||||
|     console: code.InteractiveConsole, |     console: code.InteractiveConsole, | ||||||
|     *, |     *, | ||||||
|  | @ -88,6 +108,7 @@ def run_multiline_interactive_console( | ||||||
|     if future_flags: |     if future_flags: | ||||||
|         console.compile.compiler.flags |= future_flags |         console.compile.compiler.flags |= future_flags | ||||||
| 
 | 
 | ||||||
|  |     more_lines = functools.partial(_more_lines, console) | ||||||
|     input_n = 0 |     input_n = 0 | ||||||
| 
 | 
 | ||||||
|     def maybe_run_command(statement: str) -> bool: |     def maybe_run_command(statement: str) -> bool: | ||||||
|  | @ -113,16 +134,6 @@ def maybe_run_command(statement: str) -> bool: | ||||||
| 
 | 
 | ||||||
|         return False |         return False | ||||||
| 
 | 
 | ||||||
|     def more_lines(unicodetext: str) -> bool: |  | ||||||
|         # ooh, look at the hack: |  | ||||||
|         src = _strip_final_indent(unicodetext) |  | ||||||
|         try: |  | ||||||
|             code = console.compile(src, "<stdin>", "single") |  | ||||||
|         except (OverflowError, SyntaxError, ValueError): |  | ||||||
|             return False |  | ||||||
|         else: |  | ||||||
|             return code is None |  | ||||||
| 
 |  | ||||||
|     while 1: |     while 1: | ||||||
|         try: |         try: | ||||||
|             try: |             try: | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| from test.support import force_not_colorized | from test.support import force_not_colorized | ||||||
| 
 | 
 | ||||||
| from _pyrepl.console import InteractiveColoredConsole | from _pyrepl.console import InteractiveColoredConsole | ||||||
| 
 | from _pyrepl.simple_interact import _more_lines | ||||||
| 
 | 
 | ||||||
| class TestSimpleInteract(unittest.TestCase): | class TestSimpleInteract(unittest.TestCase): | ||||||
|     def test_multiple_statements(self): |     def test_multiple_statements(self): | ||||||
|  | @ -111,3 +111,104 @@ def test_no_active_future(self): | ||||||
|             result = console.runsource(source) |             result = console.runsource(source) | ||||||
|         self.assertFalse(result) |         self.assertFalse(result) | ||||||
|         self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n") |         self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestMoreLines(unittest.TestCase): | ||||||
|  |     def test_invalid_syntax_single_line(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "if foo" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_empty_line(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_valid_single_statement(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "foo = 1" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiline_single_assignment(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         foo = [ | ||||||
|  |             1, | ||||||
|  |             2, | ||||||
|  |             3, | ||||||
|  |         ]""") | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiline_single_block(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         def foo(): | ||||||
|  |             '''docs''' | ||||||
|  | 
 | ||||||
|  |             return 1""") | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertTrue(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiple_statements_single_line(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "foo = 1;bar = 2" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiple_statements(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         import time | ||||||
|  | 
 | ||||||
|  |         foo = 1""") | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertTrue(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiple_blocks(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         from dataclasses import dataclass | ||||||
|  | 
 | ||||||
|  |         @dataclass | ||||||
|  |         class Point: | ||||||
|  |             x: float | ||||||
|  |             y: float""") | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertTrue(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiple_blocks_empty_newline(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         from dataclasses import dataclass | ||||||
|  | 
 | ||||||
|  |         @dataclass | ||||||
|  |         class Point: | ||||||
|  |             x: float | ||||||
|  |             y: float | ||||||
|  |         """) | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_multiple_blocks_indented_newline(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = ( | ||||||
|  |             "from dataclasses import dataclass\n" | ||||||
|  |             "\n" | ||||||
|  |             "@dataclass\n" | ||||||
|  |             "class Point:\n" | ||||||
|  |             "    x: float\n" | ||||||
|  |             "    y: float\n" | ||||||
|  |             "    " | ||||||
|  |         ) | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertFalse(_more_lines(console, code)) | ||||||
|  | 
 | ||||||
|  |     def test_incomplete_statement(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "if foo:" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         self.assertTrue(_more_lines(console, code)) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Miss Islington (bot)
						Miss Islington (bot)