mirror of
https://github.com/Textualize/rich.git
synced 2026-02-06 10:58:48 +00:00
Compare commits
8 Commits
361d5caf35
...
e89931be56
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e89931be56 | ||
|
|
0752ff0472 | ||
|
|
54ae0cfbb8 | ||
|
|
07edb85f7e | ||
|
|
31930ddc84 | ||
|
|
454fcfc92c | ||
|
|
13f87a4007 | ||
|
|
b5ef155de5 |
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [14.3.2] - 2026-02-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed solo ZWJ crash https://github.com/Textualize/rich/pull/3953
|
||||||
|
- Fixed control codes reporting width of 1 https://github.com/Textualize/rich/pull/3953
|
||||||
|
|
||||||
## [14.3.1] - 2026-01-24
|
## [14.3.1] - 2026-01-24
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name = "rich"
|
name = "rich"
|
||||||
homepage = "https://github.com/Textualize/rich"
|
homepage = "https://github.com/Textualize/rich"
|
||||||
documentation = "https://rich.readthedocs.io/en/latest/"
|
documentation = "https://rich.readthedocs.io/en/latest/"
|
||||||
version = "14.3.1"
|
version = "14.3.2"
|
||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
authors = ["Will McGugan <willmcgugan@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@ -55,23 +55,26 @@ def get_character_cell_size(character: str, unicode_version: str = "auto") -> in
|
|||||||
int: Number of cells (0, 1 or 2) occupied by that character.
|
int: Number of cells (0, 1 or 2) occupied by that character.
|
||||||
"""
|
"""
|
||||||
codepoint = ord(character)
|
codepoint = ord(character)
|
||||||
|
if codepoint and codepoint < 32 or 0x07F <= codepoint < 0x0A0:
|
||||||
|
return 0
|
||||||
table = load_cell_table(unicode_version).widths
|
table = load_cell_table(unicode_version).widths
|
||||||
if codepoint > table[-1][1]:
|
|
||||||
|
last_entry = table[-1]
|
||||||
|
if codepoint > last_entry[1]:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
lower_bound = 0
|
lower_bound = 0
|
||||||
upper_bound = len(table) - 1
|
upper_bound = len(table) - 1
|
||||||
index = (lower_bound + upper_bound) // 2
|
|
||||||
while True:
|
while lower_bound <= upper_bound:
|
||||||
|
index = (lower_bound + upper_bound) >> 1
|
||||||
start, end, width = table[index]
|
start, end, width = table[index]
|
||||||
if codepoint < start:
|
if codepoint < start:
|
||||||
upper_bound = index - 1
|
upper_bound = index - 1
|
||||||
elif codepoint > end:
|
elif codepoint > end:
|
||||||
lower_bound = index + 1
|
lower_bound = index + 1
|
||||||
else:
|
else:
|
||||||
return 0 if width == -1 else width
|
return width
|
||||||
if upper_bound < lower_bound:
|
|
||||||
break
|
|
||||||
index = (lower_bound + upper_bound) // 2
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@ -135,12 +138,14 @@ def _cell_len(text: str, unicode_version: str) -> int:
|
|||||||
|
|
||||||
SPECIAL = {"\u200d", "\ufe0f"}
|
SPECIAL = {"\u200d", "\ufe0f"}
|
||||||
|
|
||||||
iter_characters = iter(text)
|
index = 0
|
||||||
|
character_count = len(text)
|
||||||
|
|
||||||
for character in iter_characters:
|
while index < character_count:
|
||||||
|
character = text[index]
|
||||||
if character in SPECIAL:
|
if character in SPECIAL:
|
||||||
if character == "\u200d":
|
if character == "\u200d":
|
||||||
next(iter_characters)
|
index += 1
|
||||||
elif last_measured_character:
|
elif last_measured_character:
|
||||||
total_width += last_measured_character in cell_table.narrow_to_wide
|
total_width += last_measured_character in cell_table.narrow_to_wide
|
||||||
last_measured_character = None
|
last_measured_character = None
|
||||||
@ -148,6 +153,7 @@ def _cell_len(text: str, unicode_version: str) -> int:
|
|||||||
if character_width := get_character_cell_size(character, unicode_version):
|
if character_width := get_character_cell_size(character, unicode_version):
|
||||||
last_measured_character = character
|
last_measured_character = character
|
||||||
total_width += character_width
|
total_width += character_width
|
||||||
|
index += 1
|
||||||
|
|
||||||
return total_width
|
return total_width
|
||||||
|
|
||||||
|
|||||||
@ -83,6 +83,12 @@ def _render_segments(segments: Iterable[Segment]) -> str:
|
|||||||
|
|
||||||
def display(segments: Iterable[Segment], text: str) -> None:
|
def display(segments: Iterable[Segment], text: str) -> None:
|
||||||
"""Render segments to Jupyter."""
|
"""Render segments to Jupyter."""
|
||||||
|
segments = list(segments)
|
||||||
|
if not segments and not text:
|
||||||
|
# display() always prints a newline, so if there is no content then
|
||||||
|
# we don't want a single newline appearing.
|
||||||
|
# See https://github.com/Textualize/rich/issues/3274
|
||||||
|
return None
|
||||||
html = _render_segments(segments)
|
html = _render_segments(segments)
|
||||||
jupyter_renderable = JupyterRenderable(html, text)
|
jupyter_renderable = JupyterRenderable(html, text)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -187,3 +187,20 @@ def test_nerd_font():
|
|||||||
"""Regression test for https://github.com/Textualize/rich/issues/3943"""
|
"""Regression test for https://github.com/Textualize/rich/issues/3943"""
|
||||||
# Not allocated by unicode, but used by nerd fonts
|
# Not allocated by unicode, but used by nerd fonts
|
||||||
assert cell_len("\U000f024d") == 1
|
assert cell_len("\U000f024d") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_zwj():
|
||||||
|
"""Test special case of zero width joiners"""
|
||||||
|
assert cell_len("") == 0
|
||||||
|
assert cell_len("\u200d") == 0
|
||||||
|
assert cell_len("1\u200d") == 1
|
||||||
|
# This sequence should really produce 2, but it aligns with with wcwidth
|
||||||
|
# What gets written to the terminal is anybody's guess, I've seen multiple variations
|
||||||
|
assert cell_len("1\u200d2") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_printable():
|
||||||
|
"""Non printable characters should report a width of 0."""
|
||||||
|
for ordinal in range(31):
|
||||||
|
character = chr(ordinal)
|
||||||
|
assert cell_len(character) == 0
|
||||||
|
|||||||
@ -30,3 +30,22 @@ def test_jupyter_lines_env():
|
|||||||
console = Console(
|
console = Console(
|
||||||
width=40, _environ={"JUPYTER_LINES": "broken"}, force_jupyter=True
|
width=40, _environ={"JUPYTER_LINES": "broken"}, force_jupyter=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_jupyter_capture(monkeypatch):
|
||||||
|
# If inside a capture, ipython's display shouldn't be called,
|
||||||
|
# or we would get spurious newlines.
|
||||||
|
# See https://github.com/Textualize/rich/issues/3274
|
||||||
|
called = False
|
||||||
|
|
||||||
|
def mock_display(*args, **kwargs):
|
||||||
|
nonlocal called
|
||||||
|
called = True
|
||||||
|
|
||||||
|
monkeypatch.setattr("IPython.display.display", mock_display)
|
||||||
|
console = Console(force_jupyter=True)
|
||||||
|
with console.capture():
|
||||||
|
console.print("foo")
|
||||||
|
assert not called
|
||||||
|
console.print("foo")
|
||||||
|
assert called
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user