Skip to main content

Module geom

Module geom 

Source
Expand description

Pure geometry helpers for host-driven mouse translation.

These helpers are host-agnostic: they operate on doc-space coordinates (row/col in chars) and tab-expanded visual columns. The TUI host and any future GUI host use them independently after doing their own pixel-or-cell → visual-column conversion.

§Cell-width semantics

Both helpers measure a char with exactly the rule hjkl-buffer-tui’s paint_row uses to advance the cursor across the terminal, because being consistent with the cell the glyph was actually painted in is the entire point of these functions:

charcellswhy
\tto the next tab_width stop, measured from the visual columntab stops are screen positions, so a preceding wide char shifts them
width() == Some(w)wthe unicode-width table — 2 for CJK and most emoji, 1 for ASCII
width() == Some(0)0combining marks and variation selectors compose onto the preceding cell
width() == None1control characters; see below

Zero-width chars (combining marks, U+FE0F) advance nothing and are never a cursor landing site — visual_col_to_char_col skips them, so a click or a j into that column lands on the base char they compose onto (or, past the composed cell, on the next real char). Verified against neovim 0.12.4: on "ae\u{301}b", virtcol reports the mark and the e at the same column, and j from column 3 of an ASCII line lands on the b, never on the mark.

Control chars (width() == None) count as one cell. This deliberately diverges from vim, which renders ^A in two: paint_row resolves them through sanitize_control, which maps every C0/C1 control to a single-width Control Pictures glyph (U+0001), and then advances by ch.width().unwrap_or(1) — one cell. Matching the renderer is the contract here; matching vim’s ^X notation would put the cursor one cell left of its own glyph on every line containing a control char.

Known divergence from vim: emoji presentation sequences. vim widens U+2764 U+FE0F (“❤️”) to two cells because it resolves the sequence as a grapheme cluster. unicode-width is consulted per char here (and in paint_row), so U+2764 measures 1 (East Asian Ambiguous) and the variation selector 0 — one cell total. The two sides of hjkl agree with each other, which keeps the cursor on its glyph; they are both one cell narrower than vim would be. Fixing that means grapheme segmentation in the renderer first.

Functions§

char_col_to_visual_col
Map a character index in line to its starting visual column (the screen-cell offset from line start of the char’s FIRST cell). The inverse of visual_col_to_char_col.
visual_col_to_char_col
Inverse of char_col_to_visual_col.