* Calculate the terminal width of a single grapheme cluster. * Based on code from the string-width library, but includes a possible-emoji * check to avoid running the RGI_Emoji regex unnecessarily.
(segment: string)
| 165 | * check to avoid running the RGI_Emoji regex unnecessarily. |
| 166 | */ |
| 167 | function graphemeWidth(segment: string): number { |
| 168 | if (segment === "\t") { |
| 169 | return 3; |
| 170 | } |
| 171 | |
| 172 | // Zero-width clusters |
| 173 | if (zeroWidthRegex.test(segment)) { |
| 174 | return 0; |
| 175 | } |
| 176 | |
| 177 | // Emoji check with pre-filter |
| 178 | if (couldBeEmoji(segment) && rgiEmojiRegex.test(segment)) { |
| 179 | return 2; |
| 180 | } |
| 181 | |
| 182 | // Get base visible codepoint |
| 183 | const base = segment.replace(leadingNonPrintingRegex, ""); |
| 184 | const cp = base.codePointAt(0); |
| 185 | if (cp === undefined) { |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | // Regional indicator symbols (U+1F1E6..U+1F1FF) are often rendered as |
| 190 | // full-width emoji in terminals, even when isolated during streaming. |
| 191 | // Keep width conservative (2) to avoid terminal auto-wrap drift artifacts. |
| 192 | if (cp >= 0x1f1e6 && cp <= 0x1f1ff) { |
| 193 | return 2; |
| 194 | } |
| 195 | |
| 196 | let width = eastAsianWidth(cp); |
| 197 | |
| 198 | // Trailing halfwidth/fullwidth forms and AM vowels that segment with a base. |
| 199 | if (segment.length > 1) { |
| 200 | for (const char of segment.slice(1)) { |
| 201 | const c = char.codePointAt(0)!; |
| 202 | if (c >= 0xff00 && c <= 0xffef) { |
| 203 | width += eastAsianWidth(c); |
| 204 | } else if (c === 0x0e33 || c === 0x0eb3) { |
| 205 | width += 1; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | return width; |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Calculate the visible width of a string in terminal columns. |
no test coverage detected