okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
006e29f
upstream
1// Command widths renders scripts and emoji whose column width the two width 2// models disagree about, so drift between what ultraviolet thinks it drew and 3// what the terminal actually painted is visible on screen. 4// 5// Every sample sits in a box of the same inner width, padded out using 6// ultraviolet's own measurement. If the width model matches the terminal, the 7// right edges form a straight column. Any edge that sits left or right of the 8// rest is the renderer and the terminal disagreeing about that sample, which is 9// the bug this example exists to show. 10// 11// Press w to force wcwidth, g to force grapheme width, q to quit. Terminals 12// that implement DEC mode 2027 line up under grapheme width; everything else 13// lines up under wcwidth. 14package main 15 16import ( 17"fmt" 18"log" 19"strings" 20 21 uv"github.com/charmbracelet/ultraviolet" 22"github.com/charmbracelet/x/ansi" 23) 24 25var samples = []struct { 26name string 27text string 28}{ 29 {"ascii" ,"hello world" }, 30 {"hindi" ,"नमस्ते दुनिया" }, 31 {"hindi conjunct" ,"स्ते क्ष त्र ज्ञ" }, 32 {"combining" ,"café résumé" }, 33 {"cjk" ,"世界你好" }, 34 {"emoji" ,"🌈 🔥 🦄" }, 35 {"zwj family" ,"👨👩👧👦" }, 36 {"zwj work" ,"👨💻 👩🚀" }, 37 {"zwj flag" ,"🏳️🌈" }, 38 {"skin tone" ,"👍🏽 👋🏿" }, 39 {"vs16" ,"⚠️ ❤️ ✈️" }, 40 {"keycap" ,"1️⃣ 2️⃣ 3️⃣" }, 41 {"flags" ,"🇺🇸 🇯🇵" }, 42} 43 44// boxWidth is the inner width every sample is padded to. 45const boxWidth = 24 46 47func main () { 48t := uv .DefaultTerminal () 49scr := t .Screen () 50 51if err := t .Start ();err != nil { 52log .Fatalf ("failed to start: %v" ,err ) 53 } 54defer t .Stop ()//nolint:errcheck 55 56scr .EnterAltScreen () 57 58// forced reports whether the width method was pinned from the keyboard, 59// which is what makes the two models comparable on the same terminal. 60var forced string 61 62display := func () { 63var b strings.Builder 64 65method := "wcwidth" 66if scr .WidthMethod ()== ansi .GraphemeWidth { 67method = "grapheme (DEC mode 2027)" 68 } 69fmt .Fprintf (& b ,"width method: %s" ,method ) 70if forced != "" { 71fmt .Fprintf (& b ," [forced with %q]" ,forced ) 72 } 73b .WriteString ("\n" ) 74b .WriteString ("the right edges below should form one straight column\n\n" ) 75 76// A ruler, so a drifting edge can be read off in columns. 77ruler := strings .Repeat ("....|....+" , (boxWidth + 12 )/ 10 + 1 ) 78b .WriteString (ruler [:boxWidth + 2 ]+ "\n" ) 79 80for _ ,s := range samples { 81w := scr .StringWidth (s .text ) 82pad := boxWidth - w 83if pad < 0 { 84pad = 0 85 } 86fmt .Fprintf (& b ,"│%s%s│ uv=%-2d ansi=%-2d %s\n" , 87s .text ,strings .Repeat (" " ,pad ),w , 88ansi .StringWidth (s .text ),s .name ) 89 } 90 91b .WriteString ("\nw: force wcwidth g: force grapheme width q: quit" ) 92 93uv .NewStyledString (b .String ()).Draw (scr ,scr .Bounds ()) 94scr .Render ()//nolint:errcheck 95scr .Flush ()//nolint:errcheck 96 } 97 98display () 99 100for ev := range t .Events () { 101switch ev := ev .(type ) { 102case uv.WindowSizeEvent : 103scr .Resize (ev .Width ,ev .Height ) 104display () 105case uv.KeyPressEvent : 106switch { 107case ev .MatchString ("w" ): 108scr .SetWidthMethod (ansi .WcWidth ) 109forced = "w" 110display () 111case ev .MatchString ("g" ): 112scr .SetWidthMethod (ansi .GraphemeWidth ) 113forced = "g" 114display () 115default : 116return 117 } 118 } 119 } 120}