Unify terminal display-width helpers

key_help, selector and control each carried their own East Asian width
and truncation logic with slightly different rules, and cli clipped tmux
labels by code point, so a CJK display name ran twice as wide as an
ASCII one before its ellipsis appeared.

Move the cell-width helpers into herdr_hub.text, make truncate_label
cell-aware, and sanitise control characters before curses draws a row
so an escape sequence in a remote error cannot be rendered as ^[ and
misalign the list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 03:09:28 +00:00
co-authored by Claude Fable 5.1
parent d2228ed5fb
commit ac6bb30f47
7 changed files with 194 additions and 136 deletions
+47
View File
@@ -0,0 +1,47 @@
from __future__ import annotations
import unittest
from herdr_hub.cli import truncate_label
from herdr_hub.text import (
display_tail,
display_width,
input_display_line,
pad_display,
sanitize_display,
truncate_display,
wrap_display,
)
class DisplayWidthTests(unittest.TestCase):
def test_widths_count_cells_not_code_points(self) -> None:
self.assertEqual(display_width("Rename: work"), 12)
self.assertEqual(display_width("显示名称: 工作"), 14)
self.assertEqual(display_width("e\N{COMBINING ACUTE ACCENT}"), 1)
self.assertEqual(display_width("a\N{ZERO WIDTH JOINER}b"), 2)
self.assertEqual(truncate_display("主机abc", 5), "主机a")
self.assertEqual(truncate_display("abc", 0), "")
self.assertEqual(display_tail("abc主机", 5), "c主机")
self.assertEqual(display_tail("abc主机", 3), "机")
self.assertEqual(pad_display("主机", 6), "主机 ")
self.assertEqual(display_width(pad_display("主机abc", 4)), 4)
self.assertEqual(input_display_line("名称", "abcdefgh", 9), "名称: …gh")
def test_wrapping_and_sanitising_keep_control_characters_off_screen(self) -> None:
self.assertEqual(wrap_display("ab\ncd", 10), ["ab", "cd"])
self.assertEqual(wrap_display("主机主机", 4), ["主机", "主机"])
self.assertEqual(wrap_display("a\x1bb\x7fc", 10), ["a b c"])
self.assertEqual(wrap_display("", 10), [""])
self.assertEqual(sanitize_display("x\x1b[31my"), "x [31my")
def test_labels_are_clipped_by_cells(self) -> None:
self.assertEqual(truncate_label("abc", 10), "abc")
self.assertEqual(truncate_label("tab\there", 10), "tab?here")
# Ten cells hold four CJK characters plus the ellipsis.
self.assertEqual(truncate_label("客厅开发机器人工作站", 10), "客厅开发…")
self.assertEqual(truncate_label("a" * 12, 10), "a" * 9 + "…")
if __name__ == "__main__":
unittest.main()