Startup box: server.run() prints app name, version and connect URL in a rounded unicode box on stderr. Shared termwidth module for ANSI-aware unicode display width and padding.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""Terminal display width calculation for unicode and ANSI-colored text."""
|
||||
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;:]*[A-Za-z]")
|
||||
|
||||
|
||||
def _is_wide(char: str) -> bool:
|
||||
"""Return True for characters rendered as two terminal columns."""
|
||||
if unicodedata.east_asian_width(char) in {"F", "W"}:
|
||||
return True
|
||||
cp = ord(char)
|
||||
return unicodedata.category(char) == "So" and (
|
||||
0x2600 <= cp <= 0x27BF or 0x1F300 <= cp <= 0x1F9FF or 0x1FA00 <= cp <= 0x1FAFF
|
||||
)
|
||||
|
||||
|
||||
def display_width(text: str) -> int:
|
||||
"""Calculate the display width of a string in terminal columns.
|
||||
|
||||
ANSI escape codes are ignored. Wide characters (East Asian F/W and
|
||||
emoji) count as two columns, combining marks and format characters
|
||||
(e.g. emoji variation selectors) as zero.
|
||||
"""
|
||||
plain = ANSI_ESCAPE_RE.sub("", text)
|
||||
width = 0
|
||||
for char in plain:
|
||||
if unicodedata.category(char) in {"Mn", "Mc", "Me", "Cf"}:
|
||||
continue
|
||||
width += 2 if _is_wide(char) else 1
|
||||
return width
|
||||
|
||||
|
||||
def pad_display(text: str, width: int) -> str:
|
||||
"""Pad text with trailing spaces to the given display width (no truncation)."""
|
||||
return text + " " * max(width - display_width(text), 0)
|
||||
Reference in New Issue
Block a user