Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5091c44

Browse filesBrowse files
aelsayed95ambv
andauthored
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>
1 parent e6572e8 commit 5091c44
Copy full SHA for 5091c44

File tree

5 files changed

+79
-9
lines changed
Filter options

5 files changed

+79
-9
lines changed

‎Lib/_pyrepl/historical_reader.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/historical_reader.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def select_item(self, i: int) -> None:
259259
self.transient_history[self.historyi] = self.get_unicode()
260260
buf = self.transient_history.get(i)
261261
if buf is None:
262-
buf = self.history[i]
262+
buf = self.history[i].rstrip()
263263
self.buffer = list(buf)
264264
self.historyi = i
265265
self.pos = len(self.buffer)

‎Lib/_pyrepl/readline.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/readline.py
+15-2Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,27 @@ def do(self) -> None:
244244
r: ReadlineAlikeReader
245245
r = self.reader # type: ignore[assignment]
246246
r.dirty = True # this is needed to hide the completion menu, if visible
247-
#
247+
248248
# if there are already several lines and the cursor
249249
# is not on the last one, always insert a new \n.
250250
text = r.get_unicode()
251+
251252
if "\n" in r.buffer[r.pos :] or (
252253
r.more_lines is not None and r.more_lines(text)
253254
):
254-
#
255+
def _newline_before_pos():
256+
before_idx = r.pos - 1
257+
while before_idx > 0 and text[before_idx].isspace():
258+
before_idx -= 1
259+
return text[before_idx : r.pos].count("\n") > 0
260+
261+
# if there's already a new line before the cursor then
262+
# even if the cursor is followed by whitespace, we assume
263+
# the user is trying to terminate the block
264+
if _newline_before_pos() and text[r.pos:].isspace():
265+
self.finish = True
266+
return
267+
255268
# auto-indent the next line like the previous line
256269
prevlinestart, indent = _get_previous_line_indent(r.buffer, r.pos)
257270
r.insert("\n")

‎Lib/test/test_pyrepl/test_pyrepl.py

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_pyrepl.py
+14-5Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,12 +405,21 @@ def test_multiline_edit(self):
405405
[
406406
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
407407
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
408-
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
409-
Event(evt="key", data="right", raw=bytearray(b"\x1bOC")),
410-
Event(evt="key", data="backspace", raw=bytearray(b"\x7f")),
408+
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
409+
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
410+
Event(evt="key", data="left", raw=bytearray(b"\x1bOD")),
411+
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
411412
Event(evt="key", data="g", raw=bytearray(b"g")),
412413
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
413-
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
414+
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
415+
Event(evt="key", data="delete", raw=bytearray(b"\x7F")),
416+
Event(evt="key", data="right", raw=bytearray(b"g")),
417+
Event(evt="key", data="backspace", raw=bytearray(b"\x08")),
418+
Event(evt="key", data="p", raw=bytearray(b"p")),
419+
Event(evt="key", data="a", raw=bytearray(b"a")),
420+
Event(evt="key", data="s", raw=bytearray(b"s")),
421+
Event(evt="key", data="s", raw=bytearray(b"s")),
422+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
414423
Event(evt="key", data="\n", raw=bytearray(b"\n")),
415424
],
416425
)
@@ -419,7 +428,7 @@ def test_multiline_edit(self):
419428
output = multiline_input(reader)
420429
self.assertEqual(output, "def f():\n ...\n ")
421430
output = multiline_input(reader)
422-
self.assertEqual(output, "def g():\n ...\n ")
431+
self.assertEqual(output, "def g():\n pass\n ")
423432

424433
def test_history_navigation_with_up_arrow(self):
425434
events = itertools.chain(

‎Lib/test/test_pyrepl/test_reader.py

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_reader.py
+44-1Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import itertools
2+
import functools
23
from unittest import TestCase
34

4-
from .support import handle_all_events, handle_events_narrow_console, code_to_events
5+
from .support import handle_all_events, handle_events_narrow_console, code_to_events, prepare_reader
56
from _pyrepl.console import Event
67

78

@@ -133,3 +134,45 @@ def test_up_arrow_after_ctrl_r(self):
133134

134135
reader, _ = handle_all_events(events)
135136
self.assert_screen_equals(reader, "")
137+
138+
def test_newline_within_block_trailing_whitespace(self):
139+
# fmt: off
140+
code = (
141+
"def foo():\n"
142+
"a = 1\n"
143+
)
144+
# fmt: on
145+
146+
events = itertools.chain(
147+
code_to_events(code),
148+
[
149+
# go to the end of the first line
150+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
151+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
152+
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
153+
# new lines in-block shouldn't terminate the block
154+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
155+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
156+
# end of line 2
157+
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
158+
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
159+
# a double new line in-block should terminate the block
160+
# even if its followed by whitespace
161+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
162+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
163+
],
164+
)
165+
166+
no_paste_reader = functools.partial(prepare_reader, paste_mode=False)
167+
reader, _ = handle_all_events(events, prepare_reader=no_paste_reader)
168+
169+
expected = (
170+
"def foo():\n"
171+
"\n"
172+
"\n"
173+
" a = 1\n"
174+
" \n"
175+
" " # HistoricalReader will trim trailing whitespace
176+
)
177+
self.assert_screen_equals(reader, expected)
178+
self.assertTrue(reader.finished)
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
In PyREPL, updated ``maybe-accept``'s logic so that if the user hits
2+
:kbd:`Enter` twice, they are able to terminate the block even if there's
3+
trailing whitespace. Also, now when the user hits arrow up, the cursor
4+
is on the last functional line. This matches IPython's behavior.
5+
Patch by Aya Elsayed.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.