gh-118911: Trailing whitespace in a block shouldn't prevent the user from terminating the code block (#119355)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
e6572e8f98
commit
5091c4400c
@ -259,7 +259,7 @@ class HistoricalReader(Reader):
|
|||||||
self.transient_history[self.historyi] = self.get_unicode()
|
self.transient_history[self.historyi] = self.get_unicode()
|
||||||
buf = self.transient_history.get(i)
|
buf = self.transient_history.get(i)
|
||||||
if buf is None:
|
if buf is None:
|
||||||
buf = self.history[i]
|
buf = self.history[i].rstrip()
|
||||||
self.buffer = list(buf)
|
self.buffer = list(buf)
|
||||||
self.historyi = i
|
self.historyi = i
|
||||||
self.pos = len(self.buffer)
|
self.pos = len(self.buffer)
|
||||||
|
@ -244,14 +244,27 @@ class maybe_accept(commands.Command):
|
|||||||
r: ReadlineAlikeReader
|
r: ReadlineAlikeReader
|
||||||
r = self.reader # type: ignore[assignment]
|
r = self.reader # type: ignore[assignment]
|
||||||
r.dirty = True # this is needed to hide the completion menu, if visible
|
r.dirty = True # this is needed to hide the completion menu, if visible
|
||||||
#
|
|
||||||
# if there are already several lines and the cursor
|
# if there are already several lines and the cursor
|
||||||
# is not on the last one, always insert a new \n.
|
# is not on the last one, always insert a new \n.
|
||||||
text = r.get_unicode()
|
text = r.get_unicode()
|
||||||
|
|
||||||
if "\n" in r.buffer[r.pos :] or (
|
if "\n" in r.buffer[r.pos :] or (
|
||||||
r.more_lines is not None and r.more_lines(text)
|
r.more_lines is not None and r.more_lines(text)
|
||||||
):
|
):
|
||||||
#
|
def _newline_before_pos():
|
||||||
|
before_idx = r.pos - 1
|
||||||
|
while before_idx > 0 and text[before_idx].isspace():
|
||||||
|
before_idx -= 1
|
||||||
|
return text[before_idx : r.pos].count("\n") > 0
|
||||||
|
|
||||||
|
# if there's already a new line before the cursor then
|
||||||
|
# even if the cursor is followed by whitespace, we assume
|
||||||
|
# the user is trying to terminate the block
|
||||||
|
if _newline_before_pos() and text[r.pos:].isspace():
|
||||||
|
self.finish = True
|
||||||
|
return
|
||||||
|
|
||||||
# auto-indent the next line like the previous line
|
# auto-indent the next line like the previous line
|
||||||
prevlinestart, indent = _get_previous_line_indent(r.buffer, r.pos)
|
prevlinestart, indent = _get_previous_line_indent(r.buffer, r.pos)
|
||||||
r.insert("\n")
|
r.insert("\n")
|
||||||
|
@ -405,12 +405,21 @@ class TestPyReplOutput(TestCase):
|
|||||||
[
|
[
|
||||||
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="up", raw=bytearray(b"\x1bOA")),
|
||||||
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
|
||||||
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
|
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
|
||||||
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
|
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
|
||||||
|
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
|
||||||
Event(evt="key", data="g", raw=bytearray(b"g")),
|
Event(evt="key", data="g", raw=bytearray(b"g")),
|
||||||
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
|
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
|
||||||
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
|
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
|
||||||
|
Event(evt="key", data="delete", raw=bytearray(b"\x7F")),
|
||||||
|
Event(evt="key", data="right", raw=bytearray(b"g")),
|
||||||
|
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
|
||||||
|
Event(evt="key", data="p", raw=bytearray(b"p")),
|
||||||
|
Event(evt="key", data="a", raw=bytearray(b"a")),
|
||||||
|
Event(evt="key", data="s", raw=bytearray(b"s")),
|
||||||
|
Event(evt="key", data="s", raw=bytearray(b"s")),
|
||||||
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -419,7 +428,7 @@ class TestPyReplOutput(TestCase):
|
|||||||
output = multiline_input(reader)
|
output = multiline_input(reader)
|
||||||
self.assertEqual(output, "def f():\n ...\n ")
|
self.assertEqual(output, "def f():\n ...\n ")
|
||||||
output = multiline_input(reader)
|
output = multiline_input(reader)
|
||||||
self.assertEqual(output, "def g():\n ...\n ")
|
self.assertEqual(output, "def g():\n pass\n ")
|
||||||
|
|
||||||
def test_history_navigation_with_up_arrow(self):
|
def test_history_navigation_with_up_arrow(self):
|
||||||
events = itertools.chain(
|
events = itertools.chain(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import itertools
|
import itertools
|
||||||
|
import functools
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from .support import handle_all_events, handle_events_narrow_console, code_to_events
|
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
|
||||||
from _pyrepl.console import Event
|
from _pyrepl.console import Event
|
||||||
|
|
||||||
|
|
||||||
@ -133,3 +134,45 @@ class TestReader(TestCase):
|
|||||||
|
|
||||||
reader, _ = handle_all_events(events)
|
reader, _ = handle_all_events(events)
|
||||||
self.assert_screen_equals(reader, "")
|
self.assert_screen_equals(reader, "")
|
||||||
|
|
||||||
|
def test_newline_within_block_trailing_whitespace(self):
|
||||||
|
# fmt: off
|
||||||
|
code = (
|
||||||
|
"def foo():\n"
|
||||||
|
"a = 1\n"
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
events = itertools.chain(
|
||||||
|
code_to_events(code),
|
||||||
|
[
|
||||||
|
# go to the end of the first line
|
||||||
|
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
||||||
|
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
|
||||||
|
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
|
||||||
|
# new lines in-block shouldn't terminate the block
|
||||||
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
|
# end of line 2
|
||||||
|
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
|
||||||
|
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
|
||||||
|
# a double new line in-block should terminate the block
|
||||||
|
# even if its followed by whitespace
|
||||||
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
|
Event(evt="key", data="\n", raw=bytearray(b"\n")),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
no_paste_reader = functools.partial(prepare_reader, paste_mode=False)
|
||||||
|
reader, _ = handle_all_events(events, prepare_reader=no_paste_reader)
|
||||||
|
|
||||||
|
expected = (
|
||||||
|
"def foo():\n"
|
||||||
|
"\n"
|
||||||
|
"\n"
|
||||||
|
" a = 1\n"
|
||||||
|
" \n"
|
||||||
|
" " # HistoricalReader will trim trailing whitespace
|
||||||
|
)
|
||||||
|
self.assert_screen_equals(reader, expected)
|
||||||
|
self.assertTrue(reader.finished)
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
In PyREPL, updated ``maybe-accept``'s logic so that if the user hits
|
||||||
|
:kbd:`Enter` twice, they are able to terminate the block even if there's
|
||||||
|
trailing whitespace. Also, now when the user hits arrow up, the cursor
|
||||||
|
is on the last functional line. This matches IPython's behavior.
|
||||||
|
Patch by Aya Elsayed.
|
Loading…
x
Reference in New Issue
Block a user