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 dae0375

Browse filesBrowse files
authored
gh-111201: Improve pyrepl auto indentation (#119606)
- auto-indent when editing multi-line block - ignore comments
1 parent 94e9585 commit dae0375
Copy full SHA for dae0375

File tree

Expand file treeCollapse file tree

3 files changed

+101
-11
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+101
-11
lines changed

‎Lib/_pyrepl/readline.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/readline.py
+19-8Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,24 @@ def _get_first_indentation(buffer: list[str]) -> str | None:
237237
return None
238238

239239

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

248259

249260
class maybe_accept(commands.Command):
@@ -280,7 +291,7 @@ def _newline_before_pos():
280291
for i in range(prevlinestart, prevlinestart + indent):
281292
r.insert(r.buffer[i])
282293
r.update_last_used_indentation()
283-
if _is_last_char_colon(r.buffer):
294+
if _should_auto_indent(r.buffer, r.pos):
284295
if r.last_used_indentation is not None:
285296
indentation = r.last_used_indentation
286297
else:

‎Lib/test/test_pyrepl/test_pyrepl.py

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_pyrepl.py
+80-1Lines changed: 80 additions & 1 deletion
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

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_reader.py
+2-2Lines changed: 2 additions & 2 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.