okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Drew Smirnofffix: draw graphene clusters wider than 4 columns (#150)006e29f

upstream
3.2 KiB120 linesraw
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 {
26	name string
27	text 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() {
48	t := uv.DefaultTerminal()
49	scr := t.Screen()
50
51	if err := t.Start(); err != nil {
52		log.Fatalf("failed to start: %v", err)
53	}
54	defer t.Stop() //nolint:errcheck
55
56	scr.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.
60	var forced string
61
62	display := func() {
63		var b strings.Builder
64
65		method := "wcwidth"
66		if scr.WidthMethod() == ansi.GraphemeWidth {
67			method = "grapheme (DEC mode 2027)"
68		}
69		fmt.Fprintf(&b, "width method: %s", method)
70		if forced != "" {
71			fmt.Fprintf(&b, "  [forced with %q]", forced)
72		}
73		b.WriteString("\n")
74		b.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.
77		ruler := strings.Repeat("....|....+", (boxWidth+12)/10+1)
78		b.WriteString(ruler[:boxWidth+2] + "\n")
79
80		for _, s := range samples {
81			w := scr.StringWidth(s.text)
82			pad := boxWidth - w
83			if pad < 0 {
84				pad = 0
85			}
86			fmt.Fprintf(&b, "│%s%s│ uv=%-2d ansi=%-2d  %s\n",
87				s.text, strings.Repeat(" ", pad), w,
88				ansi.StringWidth(s.text), s.name)
89		}
90
91		b.WriteString("\nw: force wcwidth   g: force grapheme width   q: quit")
92
93		uv.NewStyledString(b.String()).Draw(scr, scr.Bounds())
94		scr.Render() //nolint:errcheck
95		scr.Flush()  //nolint:errcheck
96	}
97
98	display()
99
100	for ev := range t.Events() {
101		switch ev := ev.(type) {
102		case uv.WindowSizeEvent:
103			scr.Resize(ev.Width, ev.Height)
104			display()
105		case uv.KeyPressEvent:
106			switch {
107			case ev.MatchString("w"):
108				scr.SetWidthMethod(ansi.WcWidth)
109				forced = "w"
110				display()
111			case ev.MatchString("g"):
112				scr.SetWidthMethod(ansi.GraphemeWidth)
113				forced = "g"
114				display()
115			default:
116				return
117			}
118		}
119	}
120}