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:
+3
-24
@@ -15,12 +15,12 @@ import threading
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Callable, Sequence
|
||||
import uuid
|
||||
import unicodedata
|
||||
|
||||
from .remotes import (
|
||||
RemoteEntry, clip_utf8_tail, decode_remote_output, parse_remote_session_result,
|
||||
remote_control_command, remote_session_command,
|
||||
)
|
||||
from .text import wrap_display
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .cli import Endpoint
|
||||
@@ -431,27 +431,6 @@ class HerdrControl:
|
||||
return tab_id
|
||||
|
||||
|
||||
def display_chunks(text: str, width: int) -> list[str]:
|
||||
chunks: list[str] = []
|
||||
line = ""
|
||||
cells = 0
|
||||
for character in text:
|
||||
if character == "\n":
|
||||
chunks.append(line)
|
||||
line, cells = "", 0
|
||||
continue
|
||||
if ord(character) < 32 or ord(character) == 127:
|
||||
character = " "
|
||||
size = 0 if unicodedata.combining(character) else (2 if unicodedata.east_asian_width(character) in ("W", "F") else 1)
|
||||
if cells + size > width and line:
|
||||
chunks.append(line)
|
||||
line, cells = "", 0
|
||||
line += character
|
||||
cells += size
|
||||
chunks.append(line)
|
||||
return chunks
|
||||
|
||||
|
||||
class ControlMenu:
|
||||
"""Small action menu; all subprocess work stays off the curses thread."""
|
||||
ACTIONS = (
|
||||
@@ -570,7 +549,7 @@ class ControlMenu:
|
||||
self._add(screen, y + 1, 4, detail, curses.A_DIM)
|
||||
|
||||
status_top = action_top + len(self.ACTIONS) * action_stride + (0 if compact else 1)
|
||||
status_lines = display_chunks(status, max(1, width - 5)) if status else []
|
||||
status_lines = wrap_display(status, max(1, width - 5)) if status else []
|
||||
visible_lines = max(1, height - status_top - 1)
|
||||
status_offset = min(status_offset, max(0, len(status_lines) - visible_lines))
|
||||
if future is not None and phase in ("update", "update-restart"):
|
||||
@@ -664,7 +643,7 @@ class ControlMenu:
|
||||
height, width = screen.getmaxyx()
|
||||
if y < 0 or y >= height or x >= width:
|
||||
return
|
||||
chunks = display_chunks(value, max(1, width - x - 1))
|
||||
chunks = wrap_display(value, max(1, width - x - 1))
|
||||
try:
|
||||
screen.addstr(y, x, chunks[0] if chunks else "", attribute)
|
||||
except curses.error:
|
||||
|
||||
Reference in New Issue
Block a user