Skip to content

Commit

Permalink
[regex,input_field]: Add/clarify wide char support (fixes #98, #126)
Browse files Browse the repository at this point in the history
InputField in its current form can't support wide characters, but other
things now do. Real length also uses `wcswidth` now on top of stripping
ANSI.

Credit to @qoft with #118.
  • Loading branch information
bczsalba committed Feb 24, 2024
1 parent 196f71a commit 71acb88
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 5 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ description = """\
license = {text = "MIT"}

requires-python = ">=3.8"
dependencies = ["typing_extensions"]
dependencies = ["wcwidth", "typing_extensions"]

keywords = [
"terminal",
Expand Down
4 changes: 3 additions & 1 deletion pytermgui/regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from functools import lru_cache
from typing import Match

from wcwidth import wcswidth

RE_LINK = re.compile(r"(?:\x1b\]8;;([^\\]*)\x1b\\([^\\]*?)\x1b\]8;;\x1b\\)")
RE_ANSI_NEW = re.compile(rf"(\x1b\[(.*?)[mH])|{RE_LINK.pattern}|(\x1b_G(.*?)\x1b\\)")
RE_ANSI = re.compile(r"(?:\x1b\[(.*?)[mH])|(?:\x1b\](.*?)\x1b\\)|(?:\x1b_G(.*?)\x1b\\)")
Expand Down Expand Up @@ -69,7 +71,7 @@ def real_length(text: str) -> int:
The display-length of text.
"""

return len(strip_ansi(text))
return wcswidth(strip_ansi(text))


def escape_markup(text: str) -> str:
Expand Down
12 changes: 9 additions & 3 deletions pytermgui/widgets/input_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from dataclasses import dataclass
from typing import Any, Iterator, Literal

from wcwidth import wcwidth

from ..ansi_interface import MouseAction, MouseEvent
from ..enums import HorizontalAlignment
from ..helpers import break_line
Expand Down Expand Up @@ -83,6 +85,9 @@ def __init__(
if "width" not in attrs:
self.width = len(value)

if any(wcwidth(char) > 1 for char in value):
raise ValueError("InputField doesn't support wide unicode characters.")

self.prompt = prompt
self.height = 1
self.tablength = tablength
Expand Down Expand Up @@ -431,6 +436,7 @@ def move_cursor(self, new: tuple[int, int], *, absolute: bool = False) -> None:
row, col = self.cursor

line = self._lines[row]
width = len(line)

# Going left, possibly upwards
if col < 0:
Expand All @@ -440,17 +446,17 @@ def move_cursor(self, new: tuple[int, int], *, absolute: bool = False) -> None:
else:
self.cursor.row -= 1
line = self._lines[self.cursor.row]
self.cursor.col = len(line)
self.cursor.col = width

# Going right, possibly downwards
elif col > len(line) and line != "":
elif col > width and line != "":
if len(self._lines) > row + 1:
self.cursor.row += 1
self.cursor.col = 0

line = self._lines[self.cursor.row]

self.cursor.col = max(0, min(self.cursor.col, len(line)))
self.cursor.col = max(0, min(self.cursor.col, width))

def get_lines(self) -> list[str]:
"""Builds the input field's lines."""
Expand Down

0 comments on commit 71acb88

Please sign in to comment.