wcwidth

repository·master·Indexed 19 days ago

https://github.com/jquast/wcwidth

A library for CLI and TUI programs to accurately measure the displayed width of Unicode strings and characters in a terminal. It provides functions like wcwidth(), wcswidth(), wcstwidth(), and width() to determine visual cell counts instead of codepoint counts. The package includes utility tools for browsing character widths (wcwidth-browser), stripping or showing terminal escape sequences, and updating Unicode character width tables.

Tokens
15.7K
Snippets
52
Records
66
Agent score
61%

What's inside wcwidth

  1. Handle Ambiguous Width characters

    master

    Some Unicode characters have "East Asian Ambiguous" (A) width. They may be displayed as 1 cell (Western) or 2 cells (CJK).

    By default, wcwidth treats these as narrow (width 1). To treat them as double-width, pass ambiguous_width=2 to any width-measuring function (e.g., wcwidth(), wcswidth(), width(), ljust(), rjust(), center(), wrap(), or clip()).

    import wcwidth
    
    # CIRCLED DIGIT ONE - ambiguous width
    wcwidth.width('\u2460') # returns 1
    wcwidth.width('\u2460', ambiguous_width=2) # returns 2
  2. How `wcwidth.iter_graphemes` works

    master

    The iter_graphemes function yields strings representing individual grapheme clusters.

    Note on Python compatibility: While its behavior matches Python 3.15's unicodedata.iter_graphemes(), there is a key difference in the return type: wcwidth.iter_graphemes yields strings, whereas unicodedata.iter_graphemes() yields unicodedata.Segment objects.

  3. Understand the difference between low-level and high-level width functions

    master

    The wcwidth library provides two tiers of functions for measuring character width:

    1. Low-level functions (wcwidth.wcwidth and wcswidth): These return -1 if any control codes are present in the input. wcswidth returns -1 if any character in a sequence contains C0 (U+0001 through U+001F) or C1 (U+007F through U+00A0) control characters.

    2. High-level functions (width and iter_graphemes): These are designed to be more robust. width never returns -1; it accepts a control_codes='parse' argument to handle control sequences (following XTerm and Kitty protocols).

    To accurately measure the width of a grapheme, you can use iter_graphemes to yield individual grapheme strings and then pass those strings to wcswidth.

  4. How Virama Conjunct Formation is handled

    master

    In Brahmic scripts, the library supports conjunct formation via Virama and Invisible_Stacker categories:

    • A Virama contributes 0 width.
    • A Consonant immediately following a Virama adds its width to the current grapheme cluster.
    • The total cluster width is capped at 2 cells.
    • Mn (Nonspacing Mark) characters do not break the conjunct context.
    • ZWJ (U+200D) after a virama is consumed without breaking the conjunct state (supporting virama + ZWJ + consonant).
  5. Install wcwidth via pip

    master

    Install or upgrade the stable version of wcwidth using pip to ensure your CLI/TUI programs measure terminal output width correctly.

    pip install --upgrade wcwidth
  6. Detect terminal ambiguous width mode

    master

    To reliably detect if a terminal is configured for "Ambiguous width as wide" mode, you can use the jquast/blessed library, which implements a method to query the terminal's cursor position and measure the difference in column reporting.

    import blessed, functools, wcwidth
    
    # Detect terminal ambiguous width as wide (2) or narrow (1)
    ambiguous_width = blessed.Terminal().detect_ambiguous_width()
    
    # Define a new 'width' function with this argument applied
    awidth = functools.partial(wcwidth.width, ambiguous_width=ambiguous_width)
    
    # result depends on attached terminal mode
    awidth('\u2460')
  7. Use wcwidth functions with SEMVER compatibility

    master
    The wcwidth package follows Semantic Versioning (SEMVER) rules. For the functions listed in the public API, you can safely use the version dependency wcwidth<1 in your requirements.txt or equivalent dependency management tools, as their signatures are guaranteed not to change.
  8. Handle control codes using the control_codes parameter

    master

    The width() function provides three modes for handling control characters and escape sequences via the control_codes argument:

    1. 'parse' (Default): Actively tracks horizontal cursor movement. It handles \b (backspace), \r (carriage return), \t (tab), and various CSI cursor movement sequences. It also supports modern protocols like OSC 66 Kitty Text Sizing.
    2. 'strict': Used when you want to ensure the string doesn't contain unpredictable terminal behavior. It behaves like 'parse' but raises a ValueError if it encounters indeterminate sequences (like vertical movement or clear) or if a cursor-left movement would move the cursor before the start of the string.
    3. 'ignore': The fastest mode. It treats all C0/C1 control characters and escape sequences as having a width of 0. Note that \t is treated as zero-width in this mode; if you need tab expansion, use str.expandtabs() on your text before calling width().
    # Strict mode will raise ValueError on indeterminate sequences
    try:
        width('\x1b[2J', control_codes='strict')
    except ValueError as e:
        print(f"Caught expected error: {e}")
    
    # Ignore mode is fast but ignores tab expansion
    # Use expandtabs() if you need tabs to count
    text = 'abc\tdef'.expandtabs(4)
    print(width(text, control_codes='ignore'))
  9. How OSC 8 hyperlink closing works

    master

    Per the OSC 8 specification, terminal emulators treat hyperlinks as a state attribute rather than nested HTML anchors. This means a close sequence (\x1b]8;;) closes the current hyperlink regardless of how many open sequences preceded it.

    Hyperlink.find_close(text, open_end) can be used to manually locate the matching close sequence in a string, starting from the position immediately following an open sequence.

  10. How kitty text sizing (OSC 66) works

    master

    The kitty text sizing protocol allows terminal applications to explicitly define how much space a piece of text occupies using the escape sequence: ESC ] 66 ; metadata ; text BEL/ST.

    Metadata Structure: Metadata consists of colon-separated key=value pairs:

    • s: scale (1-7)
    • w: width in cells (0-7)
    • n: fractional numerator (0-15)
    • d: fractional denominator (0-15)
    • v: vertical alignment (0=top, 1=bottom, 2=center)
    • h: horizontal alignment (0=left, 1=right, 2=center)

    Measurement Logic:

    • If w > 0, the width is scale * w cells.
    • If w == 0, the width is scale * wcswidth(inner_text) cells.

    Note that numerator, denominator, and alignment codes are parsed by the library but do not affect the cell count measurements.

  11. Understand VS (Variation Sequence) modes in wcwidth-browser

    master

    The browser supports two specific modes for testing Variation Selectors (VS), which are used to specify whether a character should be rendered as text or emoji style.

    VS-15 Mode (--vs15 or key 5)

    • Renders characters in text style.
    • By default, it filters for a narrow base character width.

    VS-16 Mode (--vs16 or key 6)

    • Renders characters in emoji style.
    • By default, it filters for a wide base character width.

    Displaying with or without Variation Selectors

    In VS modes, you can toggle how the variation selector itself is displayed using the w key:

    • W/VS (With VS): The display shows the character combined with the variation selector (e.g., width 1 for VS-15, width 2 for VS-16).
    • WO/VS (Without VS): The display shows only the base character, using the base character's width.
  12. Update Unicode character width tables via tox

    master

    The update-tables.py script is a code generation tool used to update the Unicode code tables for wcwidth. It is typically executed through the tox automation tool rather than directly. This ensures the environment is correctly set up for the generation process.

    To run the update process, use the following command in your terminal:

    $ tox -e update