gh-111201: auto-indentation in _pyrepl (#119348)

Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
Arnon Yaari 2024-05-22 00:21:14 -04:00 committed by GitHub
parent e9875ecb5d
commit cd516cd1f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 179 additions and 58 deletions

View file

@ -5,19 +5,31 @@ import rlcompleter
from unittest import TestCase
from unittest.mock import patch
from .support import FakeConsole, handle_all_events, handle_events_narrow_console
from .support import more_lines, multiline_input, code_to_events
from .support import (
FakeConsole,
handle_all_events,
handle_events_narrow_console,
more_lines,
multiline_input,
code_to_events,
)
from _pyrepl.console import Event
from _pyrepl.readline import ReadlineAlikeReader, ReadlineConfig
from _pyrepl.readline import multiline_input as readline_multiline_input
class TestCursorPosition(TestCase):
def prepare_reader(self, events):
console = FakeConsole(events)
config = ReadlineConfig(readline_completer=None)
reader = ReadlineAlikeReader(console=console, config=config)
return reader
def test_up_arrow_simple(self):
# fmt: off
code = (
'def f():\n'
' ...\n'
"def f():\n"
" ...\n"
)
# fmt: on
events = itertools.chain(
@ -34,8 +46,8 @@ class TestCursorPosition(TestCase):
def test_down_arrow_end_of_input(self):
# fmt: off
code = (
'def f():\n'
' ...\n'
"def f():\n"
" ...\n"
)
# fmt: on
events = itertools.chain(
@ -300,6 +312,79 @@ class TestCursorPosition(TestCase):
self.assertEqual(reader.pos, 10)
self.assertEqual(reader.cxy, (1, 1))
def test_auto_indent_default(self):
# fmt: off
input_code = (
"def f():\n"
"pass\n\n"
)
output_code = (
"def f():\n"
" pass\n"
" "
)
# fmt: on
def test_auto_indent_continuation(self):
# auto indenting according to previous user indentation
# fmt: off
events = itertools.chain(
code_to_events("def f():\n"),
# add backspace to delete default auto-indent
[
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
],
code_to_events(
" pass\n"
"pass\n\n"
),
)
output_code = (
"def f():\n"
" pass\n"
" pass\n"
" "
)
# fmt: on
reader = self.prepare_reader(events)
output = multiline_input(reader)
self.assertEqual(output, output_code)
def test_auto_indent_prev_block(self):
# auto indenting according to indentation in different block
# fmt: off
events = itertools.chain(
code_to_events("def f():\n"),
# add backspace to delete default auto-indent
[
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
],
code_to_events(
" pass\n"
"pass\n\n"
),
code_to_events(
"def g():\n"
"pass\n\n"
),
)
output_code = (
"def g():\n"
" pass\n"
" "
)
# fmt: on
reader = self.prepare_reader(events)
output1 = multiline_input(reader)
output2 = multiline_input(reader)
self.assertEqual(output2, output_code)
class TestPyReplOutput(TestCase):
def prepare_reader(self, events):
@ -316,14 +401,12 @@ class TestPyReplOutput(TestCase):
def test_multiline_edit(self):
events = itertools.chain(
code_to_events("def f():\n ...\n\n"),
code_to_events("def f():\n...\n\n"),
[
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
Event(evt="key", data="g", raw=bytearray(b"g")),
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
@ -334,9 +417,9 @@ class TestPyReplOutput(TestCase):
reader = self.prepare_reader(events)
output = multiline_input(reader)
self.assertEqual(output, "def f():\n ...\n ")
self.assertEqual(output, "def f():\n ...\n ")
output = multiline_input(reader)
self.assertEqual(output, "def g():\n ...\n ")
self.assertEqual(output, "def g():\n ...\n ")
def test_history_navigation_with_up_arrow(self):
events = itertools.chain(
@ -485,6 +568,7 @@ class TestPyReplCompleter(TestCase):
@property
def test_func(self):
import warnings
warnings.warn("warnings\n")
return None
@ -508,12 +592,12 @@ class TestPasteEvent(TestCase):
def test_paste(self):
# fmt: off
code = (
'def a():\n'
' for x in range(10):\n'
' if x%2:\n'
' print(x)\n'
' else:\n'
' pass\n'
"def a():\n"
" for x in range(10):\n"
" if x%2:\n"
" print(x)\n"
" else:\n"
" pass\n"
)
# fmt: on
@ -534,10 +618,10 @@ class TestPasteEvent(TestCase):
def test_paste_mid_newlines(self):
# fmt: off
code = (
'def f():\n'
' x = y\n'
' \n'
' y = z\n'
"def f():\n"
" x = y\n"
" \n"
" y = z\n"
)
# fmt: on
@ -558,16 +642,16 @@ class TestPasteEvent(TestCase):
def test_paste_mid_newlines_not_in_paste_mode(self):
# fmt: off
code = (
'def f():\n'
' x = y\n'
' \n'
' y = z\n\n'
"def f():\n"
"x = y\n"
"\n"
"y = z\n\n"
)
expected = (
'def f():\n'
' x = y\n'
' '
"def f():\n"
" x = y\n"
" "
)
# fmt: on
@ -579,20 +663,20 @@ class TestPasteEvent(TestCase):
def test_paste_not_in_paste_mode(self):
# fmt: off
input_code = (
'def a():\n'
' for x in range(10):\n'
' if x%2:\n'
' print(x)\n'
' else:\n'
' pass\n\n'
"def a():\n"
"for x in range(10):\n"
"if x%2:\n"
"print(x)\n"
"else:\n"
"pass\n\n"
)
output_code = (
'def a():\n'
' for x in range(10):\n'
' if x%2:\n'
' print(x)\n'
' else:'
"def a():\n"
" for x in range(10):\n"
" if x%2:\n"
" print(x)\n"
" else:"
)
# fmt: on
@ -605,25 +689,25 @@ class TestPasteEvent(TestCase):
"""Test that bracketed paste using \x1b[200~ and \x1b[201~ works."""
# fmt: off
input_code = (
'def a():\n'
' for x in range(10):\n'
'\n'
' if x%2:\n'
' print(x)\n'
'\n'
' else:\n'
' pass\n'
"def a():\n"
" for x in range(10):\n"
"\n"
" if x%2:\n"
" print(x)\n"
"\n"
" else:\n"
" pass\n"
)
output_code = (
'def a():\n'
' for x in range(10):\n'
'\n'
' if x%2:\n'
' print(x)\n'
'\n'
' else:\n'
' pass\n'
"def a():\n"
" for x in range(10):\n"
"\n"
" if x%2:\n"
" print(x)\n"
"\n"
" else:\n"
" pass\n"
)
# fmt: on