Skip to content

Commit 38bf39c

Browse files
[3.13] gh-111201: Improve pyrepl auto indentation (GH-119606) (GH-119833)
- auto-indent when editing multi-line block - ignore comments (cherry picked from commit dae0375) Co-authored-by: Arnon Yaari <wiggin15@yahoo.com>
1 parent 7dae73b commit 38bf39c

File tree

3 files changed

+101
-11
lines changed

3 files changed

+101
-11
lines changed

Lib/_pyrepl/readline.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,24 @@ def _get_first_indentation(buffer: list[str]) -> str | None:
230230
return None
231231

232232

233-
def _is_last_char_colon(buffer: list[str]) -> bool:
234-
i = len(buffer)
235-
while i > 0:
236-
i -= 1
237-
if buffer[i] not in " \t\n": # ignore whitespaces
238-
return buffer[i] == ":"
239-
return False
233+
def _should_auto_indent(buffer: list[str], pos: int) -> bool:
234+
# check if last character before "pos" is a colon, ignoring
235+
# whitespaces and comments.
236+
last_char = None
237+
while pos > 0:
238+
pos -= 1
239+
if last_char is None:
240+
if buffer[pos] not in " \t\n": # ignore whitespaces
241+
last_char = buffer[pos]
242+
else:
243+
# even if we found a non-whitespace character before
244+
# original pos, we keep going back until newline is reached
245+
# to make sure we ignore comments
246+
if buffer[pos] == "\n":
247+
break
248+
if buffer[pos] == "#":
249+
last_char = None
250+
return last_char == ":"
240251

241252

242253
class maybe_accept(commands.Command):
@@ -273,7 +284,7 @@ def _newline_before_pos():
273284
for i in range(prevlinestart, prevlinestart + indent):
274285
r.insert(r.buffer[i])
275286
r.update_last_used_indentation()
276-
if _is_last_char_colon(r.buffer):
287+
if _should_auto_indent(r.buffer, r.pos):
277288
if r.last_used_indentation is not None:
278289
indentation = r.last_used_indentation
279290
else:

Lib/test/test_pyrepl/test_pyrepl.py

+80-1
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ def test_cursor_position_after_wrap_and_move_up(self):
312312
self.assertEqual(reader.pos, 10)
313313
self.assertEqual(reader.cxy, (1, 1))
314314

315+
316+
class TestPyReplAutoindent(TestCase):
317+
def prepare_reader(self, events):
318+
console = FakeConsole(events)
319+
config = ReadlineConfig(readline_completer=None)
320+
reader = ReadlineAlikeReader(console=console, config=config)
321+
return reader
322+
315323
def test_auto_indent_default(self):
316324
# fmt: off
317325
input_code = (
@@ -372,7 +380,6 @@ def test_auto_indent_prev_block(self):
372380
),
373381
)
374382

375-
376383
output_code = (
377384
"def g():\n"
378385
" pass\n"
@@ -385,6 +392,78 @@ def test_auto_indent_prev_block(self):
385392
output2 = multiline_input(reader)
386393
self.assertEqual(output2, output_code)
387394

395+
def test_auto_indent_multiline(self):
396+
# fmt: off
397+
events = itertools.chain(
398+
code_to_events(
399+
"def f():\n"
400+
"pass"
401+
),
402+
[
403+
# go to the end of the first line
404+
Event(evt="key", data="up", raw=bytearray(b"\x1bOA")),
405+
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
406+
# new line should be autoindented
407+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
408+
],
409+
code_to_events(
410+
"pass"
411+
),
412+
[
413+
# go to end of last line
414+
Event(evt="key", data="down", raw=bytearray(b"\x1bOB")),
415+
Event(evt="key", data="\x05", raw=bytearray(b"\x1bO5")),
416+
# double newline to terminate the block
417+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
418+
Event(evt="key", data="\n", raw=bytearray(b"\n")),
419+
],
420+
)
421+
422+
output_code = (
423+
"def f():\n"
424+
" pass\n"
425+
" pass\n"
426+
" "
427+
)
428+
# fmt: on
429+
430+
reader = self.prepare_reader(events)
431+
output = multiline_input(reader)
432+
self.assertEqual(output, output_code)
433+
434+
def test_auto_indent_with_comment(self):
435+
# fmt: off
436+
events = code_to_events(
437+
"def f(): # foo\n"
438+
"pass\n\n"
439+
)
440+
441+
output_code = (
442+
"def f(): # foo\n"
443+
" pass\n"
444+
" "
445+
)
446+
# fmt: on
447+
448+
reader = self.prepare_reader(events)
449+
output = multiline_input(reader)
450+
self.assertEqual(output, output_code)
451+
452+
def test_auto_indent_ignore_comments(self):
453+
# fmt: off
454+
events = code_to_events(
455+
"pass #:\n"
456+
)
457+
458+
output_code = (
459+
"pass #:"
460+
)
461+
# fmt: on
462+
463+
reader = self.prepare_reader(events)
464+
output = multiline_input(reader)
465+
self.assertEqual(output, output_code)
466+
388467

389468
class TestPyReplOutput(TestCase):
390469
def prepare_reader(self, events):

Lib/test/test_pyrepl/test_reader.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ def test_newline_within_block_trailing_whitespace(self):
168168

169169
expected = (
170170
"def foo():\n"
171-
"\n"
172-
"\n"
171+
" \n"
172+
" \n"
173173
" a = 1\n"
174174
" \n"
175175
" " # HistoricalReader will trim trailing whitespace

0 commit comments

Comments
 (0)