cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Terminals: the screen waits for the terminal's background, so a light one never flashes the dark theme

Dami committed 669bab5
Init asked for the background in a command of its own, racing the
startup probes, and drew the dark theme until the answer came back: a
frame of the wrong palette for as long as a question takes over SSH.
make screens caught it as share-presence-light drawn dark, 2 to 7 in 100
runs, the answer arriving 35 ms after the first frame.

The background is asked in the probes, before DA1; the screen stays
blank until its answer, DA1's (a terminal that doesn't say its
background) or 500 ms. The ui tests' clock (after) ends that wait at
once, and serve's test terminals answer DA1 as a terminal would.
commit
669bab5ae3ce3bccb5b6be0b3efef8670b1a3260
parent
61b082d30b9ea3e8d71ca6e991f5e51f08ccb096
author
Dami <dami.etoile@gmail.com>
kind
fix risk score 1.9 of 3, Jev's estimate
9 files changed, +121 -24
Minternal/serve/serve_test.go7++++
Minternal/ui/graphics.go19+++++++---
Minternal/ui/model.go7++--
Minternal/ui/notebook.go8+----
Minternal/ui/panel.go4+-
Minternal/ui/pauses_test.go15++++++---
Minternal/ui/prefs.go2+-
Minternal/ui/prefs_test.go30+++++++++++++++++
Ainternal/ui/termbg.go53++++++++++++++++++++++++++++++
M internal/serve/serve_test.go +7 -0
M internal/ui/graphics.go +13 -6
@@ -41,6 +41,7 @@ type terminal struct {
4141 sent map[int]string // image id -> what was sent, to send only changes4242 six sixelState // the sixel images drawn and to draw4343 blurred bool // the terminal window doesn't have focus44+ waitBG bool // the screen waits for the background (termbg.go)4445 }4546 4647 // newTerminal starts with what getenv, the terminal's environment, says:
@@ -58,13 +59,16 @@ func (t *terminal) wrap(seq string) string {
5859 }5960 6061 // probes are the startup queries: kitty graphics support, live light61-// and dark changes (mode 2031), the 16 palette colors (OSC 4), which62-// chart images and color scales draw with, the sixel color registers63-// (XTSMGRAPHICS) and, last, the primary device attributes. Terminals64-// answer in order, so the kitty and XTSMGRAPHICS replies, if any, come65-// before DA1's, which then decides between kitty images, sixel and text.62+// and dark changes (mode 2031), the background color (OSC 11), which63+// picks the theme, the 16 palette colors (OSC 4), which chart images64+// and color scales draw with, the sixel color registers (XTSMGRAPHICS)65+// and, last, the primary device attributes. Terminals answer in order,66+// so the other replies, if any, come before DA1's, which then decides67+// between kitty images, sixel and text, and ends the wait for the68+// background of a terminal that doesn't say it.6669 func (t *terminal) probes() tea.Cmd {67- return tea.Raw(t.wrap(chart.Query()) + ansi.SetModeLightDark + paletteQuery() + sixelRegistersQuery + ansi.RequestPrimaryDeviceAttributes)70+ return tea.Raw(t.wrap(chart.Query()) + ansi.SetModeLightDark + ansi.RequestBackgroundColor + paletteQuery() + sixelRegistersQuery +71+ ansi.RequestPrimaryDeviceAttributes)6872 }6973 7074 // paletteQuery asks for the 16 ANSI colors; terminals that don't answer
@@ -96,6 +100,7 @@ func (t *terminal) handle(msg tea.Msg) tea.Cmd {
96100 return tea.Raw(ansi.WindowOp(16)) // 16: report the cell size in pixels97101 }98102 case uv.PrimaryDeviceAttributesEvent:103+ t.waitBG = false // the background's answer, if any, came first99104 if slices.Contains(msg, 4) && !t.kitty && !t.tmux && !t.sixel {100105 t.sixel = true101106 return tea.Raw(ansi.WindowOp(16)) // sixel images are drawn at the cell size
@@ -116,6 +121,8 @@ func (t *terminal) handle(msg tea.Msg) tea.Cmd {
116121 // The system switched between light and dark. The terminal's117122 // background decides the theme, so ask for it again.118123 return tea.RequestBackgroundColor124+ case bgWaitedMsg:125+ t.waitBG = false119126 case tea.FocusMsg:120127 t.blurred = false121128 case tea.BlurMsg:
M internal/ui/model.go +4 -3
M internal/ui/notebook.go +1 -7
M internal/ui/panel.go +2 -2
M internal/ui/pauses_test.go +10 -5
M internal/ui/prefs.go +1 -1
M internal/ui/prefs_test.go +30 -0
A internal/ui/termbg.go +53 -0
@@ -0,0 +1,53 @@
1+package ui2+3+import (4+ "time"5+6+ tea "charm.land/bubbletea/v2"7+)8+9+// The theme follows the terminal's background: dark on a dark one,10+// light on a light one (prefs.go). Until the terminal has said which,11+// the screen stays blank rather than draw a frame in the dark theme a12+// light terminal would flash, for as long as a question takes to cross13+// an SSH connection. The wait ends with the background's answer, with14+// the answer to the device attributes asked after it (probes), which a15+// terminal that doesn't say its background sends instead, or after16+// bgPatience for one that answers neither.17+18+// bgPatience is how long the screen waits for a terminal that answers19+// nothing.20+const bgPatience = 500 * time.Millisecond21+22+// bgWaitedMsg is bgPatience over.23+type bgWaitedMsg struct{}24+25+// after is the clock the model's waits are timed on: a command that26+// gives msg once d has passed; tea.Tick's when nil. Unit tests give one27+// that holds notebooks' pauses for them to end (pauses_test.go).28+var after func(d time.Duration, msg tea.Msg) tea.Cmd29+30+// wait gives msg once d has passed, on the model's clock.31+func wait(d time.Duration, msg tea.Msg) tea.Cmd {32+ if after != nil {33+ return after(d, msg)34+ }35+ return tea.Tick(d, func(time.Time) tea.Msg { return msg })36+}37+38+// awaitBackground holds the screen until the terminal says its39+// background; probes asks.40+func (t *terminal) awaitBackground() tea.Cmd {41+ t.waitBG = true42+ return wait(bgPatience, bgWaitedMsg{})43+}44+45+// View implements tea.Model: the screen, once the theme is known.46+func (m *Model) View() tea.View {47+ if m.term.waitBG {48+ v := tea.NewView(" ") // an empty view is drawn again every frame49+ v.AltScreen = true50+ return v51+ }52+ return m.frame()53+}