aboutsummaryrefslogtreecommitdiff
path: root/app/ui/tui.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/tui.py')
-rw-r--r--app/ui/tui.py27
1 files changed, 23 insertions, 4 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py
index b22a186..60e2dda 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -37,6 +37,7 @@ import contextlib
import os
import re
import textwrap
+import unicodedata
from pathlib import Path
from typing import Callable, List, Optional, Sequence, Tuple, Union
@@ -255,13 +256,31 @@ def _hline(scr, y: int, x: int, n: int, attr: int = 0) -> None:
pass
+def _char_width(ch: str) -> int:
+ """Terminal cells CH occupies (East Asian Wide/Fullwidth count 2)."""
+ return 2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1
+
+
+def _disp_width(text: str) -> int:
+ """Terminal cells TEXT occupies (East Asian Wide/Fullwidth count 2)."""
+ return sum(_char_width(ch) for ch in text)
+
+
def _fit(text: str, width: int) -> str:
"""Truncate TEXT to WIDTH columns, appending '~' when cut."""
if width < 1:
return ""
- if len(text) <= width:
+ if _disp_width(text) <= width:
return text
- return text[: max(0, width - 1)] + "~"
+ out: List[str] = []
+ used = 0
+ for ch in text:
+ w = _char_width(ch)
+ if used + w > width - 1:
+ break
+ out.append(ch)
+ used += w
+ return "".join(out) + "~"
def _wrap_segments(segments: Sequence[Tuple[str, int]], width: int
@@ -283,13 +302,13 @@ def _wrap_segments(segments: Sequence[Tuple[str, int]], width: int
current: List[Tuple[str, int]] = []
used = 0
for text, attr in tokens:
- if current and used + len(text) > width:
+ if current and used + _disp_width(text) > width:
lines.append(current)
current, used = [], 0
if text.isspace():
continue # the break eats the whitespace it happens at
current.append((text, attr))
- used += len(text)
+ used += _disp_width(text)
if current:
lines.append(current)
return lines or [[]]