gh-131507: Clean up tests and type checking for _pyrepl (#131509)

This commit is contained in:
Łukasz Langa 2025-03-21 15:48:10 +01:00 committed by GitHub
parent d3f6063af1
commit 5d8e981c84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 234 additions and 135 deletions

View file

@ -456,7 +456,7 @@ class invalid_command(Command):
class show_history(Command):
def do(self) -> None:
from .pager import get_pager
from site import gethistoryfile # type: ignore[attr-defined]
from site import gethistoryfile
history = os.linesep.join(self.reader.history[:])
self.reader.console.restore()

View file

@ -19,7 +19,7 @@
from __future__ import annotations
import _colorize # type: ignore[import-not-found]
import _colorize
from abc import ABC, abstractmethod
import ast
@ -162,7 +162,7 @@ class InteractiveColoredConsole(code.InteractiveConsole):
*,
local_exit: bool = False,
) -> None:
super().__init__(locals=locals, filename=filename, local_exit=local_exit) # type: ignore[call-arg]
super().__init__(locals=locals, filename=filename, local_exit=local_exit)
self.can_colorize = _colorize.can_colorize()
def showsyntaxerror(self, filename=None, **kwargs):

View file

@ -4,8 +4,9 @@
[mypy]
files = Lib/_pyrepl
mypy_path = $MYPY_CONFIG_FILE_DIR/../../Misc/mypy
explicit_package_bases = True
python_version = 3.12
python_version = 3.13
platform = linux
pretty = True
@ -22,3 +23,7 @@ check_untyped_defs = False
# Various internal modules that typeshed deliberately doesn't have stubs for:
[mypy-_abc.*,_opcode.*,_overlapped.*,_testcapi.*,_testinternalcapi.*,test.*]
ignore_missing_imports = True
# Other untyped parts of the stdlib
[mypy-idlelib.*]
ignore_missing_imports = True

View file

@ -26,11 +26,11 @@ import sys
from contextlib import contextmanager
from dataclasses import dataclass, field, fields
import unicodedata
from _colorize import can_colorize, ANSIColors # type: ignore[import-not-found]
from _colorize import can_colorize, ANSIColors
from . import commands, console, input
from .utils import ANSI_ESCAPE_SEQUENCE, wlen, str_width
from .utils import wlen, unbracket, str_width
from .trace import trace
@ -421,42 +421,15 @@ class Reader:
@staticmethod
def process_prompt(prompt: str) -> tuple[str, int]:
"""Process the prompt.
r"""Return a tuple with the prompt string and its visible length.
This means calculate the length of the prompt. The character \x01
and \x02 are used to bracket ANSI control sequences and need to be
excluded from the length calculation. So also a copy of the prompt
is returned with these control characters removed."""
# The logic below also ignores the length of common escape
# sequences if they were not explicitly within \x01...\x02.
# They are CSI (or ANSI) sequences ( ESC [ ... LETTER )
# wlen from utils already excludes ANSI_ESCAPE_SEQUENCE chars,
# which breaks the logic below so we redefine it here.
def wlen(s: str) -> int:
return sum(str_width(i) for i in s)
out_prompt = ""
l = wlen(prompt)
pos = 0
while True:
s = prompt.find("\x01", pos)
if s == -1:
break
e = prompt.find("\x02", s)
if e == -1:
break
# Found start and end brackets, subtract from string length
l = l - (e - s + 1)
keep = prompt[pos:s]
l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep)))
out_prompt += keep + prompt[s + 1 : e]
pos = e + 1
keep = prompt[pos:]
l -= sum(map(wlen, ANSI_ESCAPE_SEQUENCE.findall(keep)))
out_prompt += keep
return out_prompt, l
The prompt string has the zero-width brackets recognized by shells
(\x01 and \x02) removed. The length ignores anything between those
brackets as well as any ANSI escape sequences.
"""
out_prompt = unbracket(prompt, including_content=False)
visible_prompt = unbracket(prompt, including_content=True)
return out_prompt, wlen(visible_prompt)
def bow(self, p: int | None = None) -> int:
"""Return the 0-based index of the word break preceding p most

View file

@ -32,7 +32,7 @@ import warnings
from dataclasses import dataclass, field
import os
from site import gethistoryfile # type: ignore[attr-defined]
from site import gethistoryfile
import sys
from rlcompleter import Completer as RLCompleter

View file

@ -3,6 +3,8 @@ import unicodedata
import functools
ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02")
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})
@functools.cache
@ -10,16 +12,27 @@ def str_width(c: str) -> int:
if ord(c) < 128:
return 1
w = unicodedata.east_asian_width(c)
if w in ('N', 'Na', 'H', 'A'):
if w in ("N", "Na", "H", "A"):
return 1
return 2
def wlen(s: str) -> int:
if len(s) == 1 and s != '\x1a':
if len(s) == 1 and s != "\x1a":
return str_width(s)
length = sum(str_width(i) for i in s)
# remove lengths of any escape sequences
sequence = ANSI_ESCAPE_SEQUENCE.findall(s)
ctrl_z_cnt = s.count('\x1a')
ctrl_z_cnt = s.count("\x1a")
return length - sum(len(i) for i in sequence) + ctrl_z_cnt
def unbracket(s: str, including_content: bool = False) -> str:
r"""Return `s` with \001 and \002 characters removed.
If `including_content` is True, content between \001 and \002 is also
stripped.
"""
if including_content:
return ZERO_WIDTH_BRACKET.sub("", s)
return s.translate(ZERO_WIDTH_TRANS)