feat(ports): carry terminal palette in Theme wire message
Bump the IPC protocol to version 16 for the 57-byte palette payload.
- commit
- 78552020e522b953176088c85bd47318bf5e2f5f
- author
- brice · · 2026-07-17 08:09 UTC
- parent
- c0c90a2
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +85 -74
M internal/ports/frame.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | package ports | |
| 2 | 2 | ||
| 3 | 3 | // ProtocolVersion is the current vev IPC wire protocol version. | |
| 4 | - | const ProtocolVersion uint16 = 15 | |
| 4 | + | const ProtocolVersion uint16 = 16 | |
| 5 | 5 | ||
| 6 | 6 | // MsgType identifies the kind of payload carried by a Frame. | |
| 7 | 7 | type MsgType uint8 | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | package ports | 1 | package ports | ||
| 2 | 2 | ||||
| 3 | // ProtocolVersion is the current vev IPC wire protocol version. | 3 | // ProtocolVersion is the current vev IPC wire protocol version. | ||
| 4 | - | const ProtocolVersion uint16 = 15 | 4 | + | const ProtocolVersion uint16 = 16 |
| 5 | 5 | ||||
| 6 | // MsgType identifies the kind of payload carried by a Frame. | 6 | // MsgType identifies the kind of payload carried by a Frame. | ||
| 7 | type MsgType uint8 | 7 | type MsgType uint8 | ||
M internal/ports/frame_test.go +2 -2
| @@ -3,8 +3,8 @@ package ports | |||
| 3 | 3 | import "testing" | |
| 4 | 4 | ||
| 5 | 5 | func TestProtocolVersion(t *testing.T) { | |
| 6 | - | if ProtocolVersion != 15 { | |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 15", ProtocolVersion) | |
| 6 | + | if ProtocolVersion != 16 { | |
| 7 | + | t.Fatalf("ProtocolVersion = %d, want 16", ProtocolVersion) | |
| 8 | 8 | } | |
| 9 | 9 | } | |
| 10 | 10 | ||
| @@ -3,8 +3,8 @@ package ports | |||||
| 3 | import "testing" | 3 | import "testing" | ||
| 4 | 4 | ||||
| 5 | func TestProtocolVersion(t *testing.T) { | 5 | func TestProtocolVersion(t *testing.T) { | ||
| 6 | - | if ProtocolVersion != 15 { | 6 | + | if ProtocolVersion != 16 { |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 15", ProtocolVersion) | 7 | + | t.Fatalf("ProtocolVersion = %d, want 16", ProtocolVersion) |
| 8 | } | 8 | } | ||
| 9 | } | 9 | } | ||
| 10 | 10 | ||||
M internal/ports/wire.go +42 -11
| @@ -85,8 +85,8 @@ type Resize struct { | |||
| 85 | 85 | Size domain.Size | |
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | - | // Theme reports the client's terminal foreground/background colors and | |
| 89 | - | // whether the client terminal supports truecolor. | |
| 88 | + | // Theme reports the client's terminal foreground/background colors, ANSI | |
| 89 | + | // palette entries, and whether the client terminal supports truecolor. | |
| 90 | 90 | type Theme struct { | |
| 91 | 91 | HasForeground bool | |
| 92 | 92 | Foreground renderer.RGB | |
| @@ -95,6 +95,8 @@ type Theme struct { | |||
| 95 | 95 | TrueColor bool | |
| 96 | 96 | SchemeKnown bool | |
| 97 | 97 | Light bool | |
| 98 | + | PaletteKnown uint16 | |
| 99 | + | Palette [16]renderer.RGB | |
| 98 | 100 | } | |
| 99 | 101 | ||
| 100 | 102 | // ImagePush carries a clipboard image from a remote client, to be written to | |
| @@ -464,7 +466,7 @@ func MarshalResize(m Resize) []byte { | |||
| 464 | 466 | return w.b | |
| 465 | 467 | } | |
| 466 | 468 | ||
| 467 | - | // MarshalTheme encodes m into a fixed-width Theme message payload. | |
| 469 | + | // MarshalTheme encodes m into a 57-byte fixed-width Theme message payload. | |
| 468 | 470 | func MarshalTheme(m Theme) []byte { | |
| 469 | 471 | var flags uint8 | |
| 470 | 472 | if m.HasForeground { | |
| @@ -482,14 +484,25 @@ func MarshalTheme(m Theme) []byte { | |||
| 482 | 484 | if m.Light { | |
| 483 | 485 | flags |= 0x10 | |
| 484 | 486 | } | |
| 485 | - | return []byte{ | |
| 486 | - | flags, | |
| 487 | - | m.Foreground.R, m.Foreground.G, m.Foreground.B, | |
| 488 | - | m.Background.R, m.Background.G, m.Background.B, | |
| 487 | + | ||
| 488 | + | w := payloadWriter{b: make([]byte, 0, 57)} | |
| 489 | + | w.putUint8(flags) | |
| 490 | + | w.putUint8(m.Foreground.R) | |
| 491 | + | w.putUint8(m.Foreground.G) | |
| 492 | + | w.putUint8(m.Foreground.B) | |
| 493 | + | w.putUint8(m.Background.R) | |
| 494 | + | w.putUint8(m.Background.G) | |
| 495 | + | w.putUint8(m.Background.B) | |
| 496 | + | w.putUint16(m.PaletteKnown) | |
| 497 | + | for _, color := range m.Palette { | |
| 498 | + | w.putUint8(color.R) | |
| 499 | + | w.putUint8(color.G) | |
| 500 | + | w.putUint8(color.B) | |
| 489 | 501 | } | |
| 502 | + | return w.b | |
| 490 | 503 | } | |
| 491 | 504 | ||
| 492 | - | // UnmarshalTheme decodes a fixed-width Theme message payload. | |
| 505 | + | // UnmarshalTheme decodes a 57-byte fixed-width Theme message payload. | |
| 493 | 506 | func UnmarshalTheme(b []byte) (Theme, error) { | |
| 494 | 507 | r := payloadReader{b: b} | |
| 495 | 508 | flags, err := r.getUint8() | |
| @@ -520,10 +533,12 @@ func UnmarshalTheme(b []byte) (Theme, error) { | |||
| 520 | 533 | if err != nil { | |
| 521 | 534 | return Theme{}, err | |
| 522 | 535 | } | |
| 523 | - | if err := r.done(); err != nil { | |
| 536 | + | paletteKnown, err := r.getUint16() | |
| 537 | + | if err != nil { | |
| 524 | 538 | return Theme{}, err | |
| 525 | 539 | } | |
| 526 | - | return Theme{ | |
| 540 | + | ||
| 541 | + | m := Theme{ | |
| 527 | 542 | HasForeground: flags&0x01 != 0, | |
| 528 | 543 | Foreground: renderer.RGB{R: fgR, G: fgG, B: fgB}, | |
| 529 | 544 | HasBackground: flags&0x02 != 0, | |
| @@ -531,7 +546,23 @@ func UnmarshalTheme(b []byte) (Theme, error) { | |||
| 531 | 546 | TrueColor: flags&0x04 != 0, | |
| 532 | 547 | SchemeKnown: flags&0x08 != 0, | |
| 533 | 548 | Light: flags&0x10 != 0, | |
| 534 | - | }, nil | |
| 549 | + | PaletteKnown: paletteKnown, | |
| 550 | + | } | |
| 551 | + | for i := range m.Palette { | |
| 552 | + | if m.Palette[i].R, err = r.getUint8(); err != nil { | |
| 553 | + | return Theme{}, err | |
| 554 | + | } | |
| 555 | + | if m.Palette[i].G, err = r.getUint8(); err != nil { | |
| 556 | + | return Theme{}, err | |
| 557 | + | } | |
| 558 | + | if m.Palette[i].B, err = r.getUint8(); err != nil { | |
| 559 | + | return Theme{}, err | |
| 560 | + | } | |
| 561 | + | } | |
| 562 | + | if err := r.done(); err != nil { | |
| 563 | + | return Theme{}, err | |
| 564 | + | } | |
| 565 | + | return m, nil | |
| 535 | 566 | } | |
| 536 | 567 | ||
| 537 | 568 | // UnmarshalResize decodes a Resize message payload. | |
| @@ -85,8 +85,8 @@ type Resize struct { | |||||
| 85 | Size domain.Size | 85 | Size domain.Size | ||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | - | // Theme reports the client's terminal foreground/background colors and | 88 | + | // Theme reports the client's terminal foreground/background colors, ANSI |
| 89 | - | // whether the client terminal supports truecolor. | 89 | + | // palette entries, and whether the client terminal supports truecolor. |
| 90 | type Theme struct { | 90 | type Theme struct { | ||
| 91 | HasForeground bool | 91 | HasForeground bool | ||
| 92 | Foreground renderer.RGB | 92 | Foreground renderer.RGB | ||
| @@ -95,6 +95,8 @@ type Theme struct { | |||||
| 95 | TrueColor bool | 95 | TrueColor bool | ||
| 96 | SchemeKnown bool | 96 | SchemeKnown bool | ||
| 97 | Light bool | 97 | Light bool | ||
| 98 | + | PaletteKnown uint16 | |||
| 99 | + | Palette [16]renderer.RGB | |||
| 98 | } | 100 | } | ||
| 99 | 101 | ||||
| 100 | // ImagePush carries a clipboard image from a remote client, to be written to | 102 | // ImagePush carries a clipboard image from a remote client, to be written to | ||
| @@ -464,7 +466,7 @@ func MarshalResize(m Resize) []byte { | |||||
| 464 | return w.b | 466 | return w.b | ||
| 465 | } | 467 | } | ||
| 466 | 468 | ||||
| 467 | - | // MarshalTheme encodes m into a fixed-width Theme message payload. | 469 | + | // MarshalTheme encodes m into a 57-byte fixed-width Theme message payload. |
| 468 | func MarshalTheme(m Theme) []byte { | 470 | func MarshalTheme(m Theme) []byte { | ||
| 469 | var flags uint8 | 471 | var flags uint8 | ||
| 470 | if m.HasForeground { | 472 | if m.HasForeground { | ||
| @@ -482,14 +484,25 @@ func MarshalTheme(m Theme) []byte { | |||||
| 482 | if m.Light { | 484 | if m.Light { | ||
| 483 | flags |= 0x10 | 485 | flags |= 0x10 | ||
| 484 | } | 486 | } | ||
| 485 | - | return []byte{ | 487 | + | |
| 486 | - | flags, | 488 | + | w := payloadWriter{b: make([]byte, 0, 57)} |
| 487 | - | m.Foreground.R, m.Foreground.G, m.Foreground.B, | 489 | + | w.putUint8(flags) |
| 488 | - | m.Background.R, m.Background.G, m.Background.B, | 490 | + | w.putUint8(m.Foreground.R) |
| 491 | + | w.putUint8(m.Foreground.G) | |||
| 492 | + | w.putUint8(m.Foreground.B) | |||
| 493 | + | w.putUint8(m.Background.R) | |||
| 494 | + | w.putUint8(m.Background.G) | |||
| 495 | + | w.putUint8(m.Background.B) | |||
| 496 | + | w.putUint16(m.PaletteKnown) | |||
| 497 | + | for _, color := range m.Palette { | |||
| 498 | + | w.putUint8(color.R) | |||
| 499 | + | w.putUint8(color.G) | |||
| 500 | + | w.putUint8(color.B) | |||
| 489 | } | 501 | } | ||
| 502 | + | return w.b | |||
| 490 | } | 503 | } | ||
| 491 | 504 | ||||
| 492 | - | // UnmarshalTheme decodes a fixed-width Theme message payload. | 505 | + | // UnmarshalTheme decodes a 57-byte fixed-width Theme message payload. |
| 493 | func UnmarshalTheme(b []byte) (Theme, error) { | 506 | func UnmarshalTheme(b []byte) (Theme, error) { | ||
| 494 | r := payloadReader{b: b} | 507 | r := payloadReader{b: b} | ||
| 495 | flags, err := r.getUint8() | 508 | flags, err := r.getUint8() | ||
| @@ -520,10 +533,12 @@ func UnmarshalTheme(b []byte) (Theme, error) { | |||||
| 520 | if err != nil { | 533 | if err != nil { | ||
| 521 | return Theme{}, err | 534 | return Theme{}, err | ||
| 522 | } | 535 | } | ||
| 523 | - | if err := r.done(); err != nil { | 536 | + | paletteKnown, err := r.getUint16() |
| 537 | + | if err != nil { | |||
| 524 | return Theme{}, err | 538 | return Theme{}, err | ||
| 525 | } | 539 | } | ||
| 526 | - | return Theme{ | 540 | + | |
| 541 | + | m := Theme{ | |||
| 527 | HasForeground: flags&0x01 != 0, | 542 | HasForeground: flags&0x01 != 0, | ||
| 528 | Foreground: renderer.RGB{R: fgR, G: fgG, B: fgB}, | 543 | Foreground: renderer.RGB{R: fgR, G: fgG, B: fgB}, | ||
| 529 | HasBackground: flags&0x02 != 0, | 544 | HasBackground: flags&0x02 != 0, | ||
| @@ -531,7 +546,23 @@ func UnmarshalTheme(b []byte) (Theme, error) { | |||||
| 531 | TrueColor: flags&0x04 != 0, | 546 | TrueColor: flags&0x04 != 0, | ||
| 532 | SchemeKnown: flags&0x08 != 0, | 547 | SchemeKnown: flags&0x08 != 0, | ||
| 533 | Light: flags&0x10 != 0, | 548 | Light: flags&0x10 != 0, | ||
| 534 | - | }, nil | 549 | + | PaletteKnown: paletteKnown, |
| 550 | + | } | |||
| 551 | + | for i := range m.Palette { | |||
| 552 | + | if m.Palette[i].R, err = r.getUint8(); err != nil { | |||
| 553 | + | return Theme{}, err | |||
| 554 | + | } | |||
| 555 | + | if m.Palette[i].G, err = r.getUint8(); err != nil { | |||
| 556 | + | return Theme{}, err | |||
| 557 | + | } | |||
| 558 | + | if m.Palette[i].B, err = r.getUint8(); err != nil { | |||
| 559 | + | return Theme{}, err | |||
| 560 | + | } | |||
| 561 | + | } | |||
| 562 | + | if err := r.done(); err != nil { | |||
| 563 | + | return Theme{}, err | |||
| 564 | + | } | |||
| 565 | + | return m, nil | |||
| 535 | } | 566 | } | ||
| 536 | 567 | ||||
| 537 | // UnmarshalResize decodes a Resize message payload. | 568 | // UnmarshalResize decodes a Resize message payload. | ||
M internal/ports/wire_test.go +40 -60
| @@ -121,7 +121,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||
| 121 | 121 | Env: []string{"A=B", "XY=123"}, | |
| 122 | 122 | }) | |
| 123 | 123 | want := []byte{ | |
| 124 | - | 0x00, 0x0f, 0x00, // version, intent | |
| 124 | + | 0x00, 0x10, 0x00, // version, intent | |
| 125 | 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 126 | 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | |
| 127 | 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | |
| @@ -244,65 +244,45 @@ func TestImagePushGoldenAndRoundTrip(t *testing.T) { | |||
| 244 | 244 | } | |
| 245 | 245 | ||
| 246 | 246 | func TestThemeGoldenAndRoundTrip(t *testing.T) { | |
| 247 | - | tests := []struct { | |
| 248 | - | name string | |
| 249 | - | msg Theme | |
| 250 | - | want []byte | |
| 251 | - | }{ | |
| 252 | - | { | |
| 253 | - | name: "foreground background truecolor dark scheme", | |
| 254 | - | msg: Theme{ | |
| 255 | - | HasForeground: true, | |
| 256 | - | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, | |
| 257 | - | HasBackground: true, | |
| 258 | - | Background: renderer.RGB{R: 4, G: 5, B: 6}, | |
| 259 | - | TrueColor: true, | |
| 260 | - | SchemeKnown: true, | |
| 261 | - | }, | |
| 262 | - | want: []byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, | |
| 263 | - | }, | |
| 264 | - | { | |
| 265 | - | name: "light scheme bit without known is preserved", | |
| 266 | - | msg: Theme{ | |
| 267 | - | Light: true, | |
| 268 | - | }, | |
| 269 | - | want: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | |
| 270 | - | }, | |
| 271 | - | { | |
| 272 | - | name: "foreground only no truecolor", | |
| 273 | - | msg: Theme{ | |
| 274 | - | HasForeground: true, | |
| 275 | - | Foreground: renderer.RGB{R: 10, G: 20, B: 30}, | |
| 276 | - | Background: renderer.RGB{R: 40, G: 50, B: 60}, | |
| 277 | - | }, | |
| 278 | - | want: []byte{0x01, 0x0a, 0x14, 0x1e, 0x28, 0x32, 0x3c}, | |
| 279 | - | }, | |
| 280 | - | { | |
| 281 | - | name: "empty", | |
| 282 | - | msg: Theme{}, | |
| 283 | - | want: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | |
| 284 | - | }, | |
| 285 | - | } | |
| 286 | - | ||
| 287 | - | for _, tt := range tests { | |
| 288 | - | t.Run(tt.name, func(t *testing.T) { | |
| 289 | - | got := MarshalTheme(tt.msg) | |
| 290 | - | if !bytes.Equal(got, tt.want) { | |
| 291 | - | t.Fatalf("MarshalTheme() = %#v, want %#v", got, tt.want) | |
| 292 | - | } | |
| 293 | - | back, err := UnmarshalTheme(got) | |
| 294 | - | if err != nil { | |
| 295 | - | t.Fatalf("UnmarshalTheme() error = %v", err) | |
| 296 | - | } | |
| 297 | - | if !reflect.DeepEqual(back, tt.msg) { | |
| 298 | - | t.Fatalf("round trip = %#v, want %#v", back, tt.msg) | |
| 299 | - | } | |
| 300 | - | }) | |
| 301 | - | } | |
| 302 | - | ||
| 303 | - | full := MarshalTheme(tests[0].msg) | |
| 304 | - | assertAllPrefixesFail(t, full, UnmarshalTheme) | |
| 305 | - | assertTrailingGarbageFails(t, full, UnmarshalTheme) | |
| 247 | + | palette := [16]renderer.RGB{ | |
| 248 | + | {R: 0x10, G: 0x11, B: 0x12}, {R: 0x13, G: 0x14, B: 0x15}, | |
| 249 | + | {R: 0x16, G: 0x17, B: 0x18}, {R: 0x19, G: 0x1a, B: 0x1b}, | |
| 250 | + | {R: 0x1c, G: 0x1d, B: 0x1e}, {R: 0x1f, G: 0x20, B: 0x21}, | |
| 251 | + | {R: 0x22, G: 0x23, B: 0x24}, {R: 0x25, G: 0x26, B: 0x27}, | |
| 252 | + | {R: 0x28, G: 0x29, B: 0x2a}, {R: 0x2b, G: 0x2c, B: 0x2d}, | |
| 253 | + | {R: 0x2e, G: 0x2f, B: 0x30}, {R: 0x31, G: 0x32, B: 0x33}, | |
| 254 | + | {R: 0x34, G: 0x35, B: 0x36}, {R: 0x37, G: 0x38, B: 0x39}, | |
| 255 | + | {R: 0x3a, G: 0x3b, B: 0x3c}, {R: 0x3d, G: 0x3e, B: 0x3f}, | |
| 256 | + | } | |
| 257 | + | msg := Theme{ | |
| 258 | + | HasForeground: true, | |
| 259 | + | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, | |
| 260 | + | HasBackground: true, | |
| 261 | + | Background: renderer.RGB{R: 4, G: 5, B: 6}, | |
| 262 | + | TrueColor: true, | |
| 263 | + | SchemeKnown: true, | |
| 264 | + | PaletteKnown: 0x8001, | |
| 265 | + | Palette: palette, | |
| 266 | + | } | |
| 267 | + | want := []byte{ | |
| 268 | + | 0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x80, 0x01, | |
| 269 | + | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, | |
| 270 | + | 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, | |
| 271 | + | 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, | |
| 272 | + | 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, | |
| 273 | + | 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, | |
| 274 | + | 0x3d, 0x3e, 0x3f, | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | got := MarshalTheme(msg) | |
| 278 | + | require.Len(t, got, 57) | |
| 279 | + | require.Equal(t, want, got) | |
| 280 | + | back, err := UnmarshalTheme(got) | |
| 281 | + | require.NoError(t, err) | |
| 282 | + | require.Equal(t, msg, back) | |
| 283 | + | ||
| 284 | + | assertAllPrefixesFail(t, got, UnmarshalTheme) | |
| 285 | + | assertTrailingGarbageFails(t, got, UnmarshalTheme) | |
| 306 | 286 | } | |
| 307 | 287 | ||
| 308 | 288 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | |
| @@ -121,7 +121,7 @@ func TestHelloEnvironmentCodec(t *testing.T) { | |||||
| 121 | Env: []string{"A=B", "XY=123"}, | 121 | Env: []string{"A=B", "XY=123"}, | ||
| 122 | }) | 122 | }) | ||
| 123 | want := []byte{ | 123 | want := []byte{ | ||
| 124 | - | 0x00, 0x0f, 0x00, // version, intent | 124 | + | 0x00, 0x10, 0x00, // version, intent |
| 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | 125 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||
| 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // client ID | ||
| 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | 127 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // resume token | ||
| @@ -244,65 +244,45 @@ func TestImagePushGoldenAndRoundTrip(t *testing.T) { | |||||
| 244 | } | 244 | } | ||
| 245 | 245 | ||||
| 246 | func TestThemeGoldenAndRoundTrip(t *testing.T) { | 246 | func TestThemeGoldenAndRoundTrip(t *testing.T) { | ||
| 247 | - | tests := []struct { | 247 | + | palette := [16]renderer.RGB{ |
| 248 | - | name string | 248 | + | {R: 0x10, G: 0x11, B: 0x12}, {R: 0x13, G: 0x14, B: 0x15}, |
| 249 | - | msg Theme | 249 | + | {R: 0x16, G: 0x17, B: 0x18}, {R: 0x19, G: 0x1a, B: 0x1b}, |
| 250 | - | want []byte | 250 | + | {R: 0x1c, G: 0x1d, B: 0x1e}, {R: 0x1f, G: 0x20, B: 0x21}, |
| 251 | - | }{ | 251 | + | {R: 0x22, G: 0x23, B: 0x24}, {R: 0x25, G: 0x26, B: 0x27}, |
| 252 | - | { | 252 | + | {R: 0x28, G: 0x29, B: 0x2a}, {R: 0x2b, G: 0x2c, B: 0x2d}, |
| 253 | - | name: "foreground background truecolor dark scheme", | 253 | + | {R: 0x2e, G: 0x2f, B: 0x30}, {R: 0x31, G: 0x32, B: 0x33}, |
| 254 | - | msg: Theme{ | 254 | + | {R: 0x34, G: 0x35, B: 0x36}, {R: 0x37, G: 0x38, B: 0x39}, |
| 255 | - | HasForeground: true, | 255 | + | {R: 0x3a, G: 0x3b, B: 0x3c}, {R: 0x3d, G: 0x3e, B: 0x3f}, |
| 256 | - | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, | 256 | + | } |
| 257 | - | HasBackground: true, | 257 | + | msg := Theme{ |
| 258 | - | Background: renderer.RGB{R: 4, G: 5, B: 6}, | 258 | + | HasForeground: true, |
| 259 | - | TrueColor: true, | 259 | + | Foreground: renderer.RGB{R: 1, G: 2, B: 3}, |
| 260 | - | SchemeKnown: true, | 260 | + | HasBackground: true, |
| 261 | - | }, | 261 | + | Background: renderer.RGB{R: 4, G: 5, B: 6}, |
| 262 | - | want: []byte{0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, | 262 | + | TrueColor: true, |
| 263 | - | }, | 263 | + | SchemeKnown: true, |
| 264 | - | { | 264 | + | PaletteKnown: 0x8001, |
| 265 | - | name: "light scheme bit without known is preserved", | 265 | + | Palette: palette, |
| 266 | - | msg: Theme{ | 266 | + | } |
| 267 | - | Light: true, | 267 | + | want := []byte{ |
| 268 | - | }, | 268 | + | 0x0f, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x80, 0x01, |
| 269 | - | want: []byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | 269 | + | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, |
| 270 | - | }, | 270 | + | 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, |
| 271 | - | { | 271 | + | 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, |
| 272 | - | name: "foreground only no truecolor", | 272 | + | 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, |
| 273 | - | msg: Theme{ | 273 | + | 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, |
| 274 | - | HasForeground: true, | 274 | + | 0x3d, 0x3e, 0x3f, |
| 275 | - | Foreground: renderer.RGB{R: 10, G: 20, B: 30}, | 275 | + | } |
| 276 | - | Background: renderer.RGB{R: 40, G: 50, B: 60}, | 276 | + | |
| 277 | - | }, | 277 | + | got := MarshalTheme(msg) |
| 278 | - | want: []byte{0x01, 0x0a, 0x14, 0x1e, 0x28, 0x32, 0x3c}, | 278 | + | require.Len(t, got, 57) |
| 279 | - | }, | 279 | + | require.Equal(t, want, got) |
| 280 | - | { | 280 | + | back, err := UnmarshalTheme(got) |
| 281 | - | name: "empty", | 281 | + | require.NoError(t, err) |
| 282 | - | msg: Theme{}, | 282 | + | require.Equal(t, msg, back) |
| 283 | - | want: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | 283 | + | |
| 284 | - | }, | 284 | + | assertAllPrefixesFail(t, got, UnmarshalTheme) |
| 285 | - | } | 285 | + | assertTrailingGarbageFails(t, got, UnmarshalTheme) |
| 286 | - | ||||
| 287 | - | for _, tt := range tests { | |||
| 288 | - | t.Run(tt.name, func(t *testing.T) { | |||
| 289 | - | got := MarshalTheme(tt.msg) | |||
| 290 | - | if !bytes.Equal(got, tt.want) { | |||
| 291 | - | t.Fatalf("MarshalTheme() = %#v, want %#v", got, tt.want) | |||
| 292 | - | } | |||
| 293 | - | back, err := UnmarshalTheme(got) | |||
| 294 | - | if err != nil { | |||
| 295 | - | t.Fatalf("UnmarshalTheme() error = %v", err) | |||
| 296 | - | } | |||
| 297 | - | if !reflect.DeepEqual(back, tt.msg) { | |||
| 298 | - | t.Fatalf("round trip = %#v, want %#v", back, tt.msg) | |||
| 299 | - | } | |||
| 300 | - | }) | |||
| 301 | - | } | |||
| 302 | - | ||||
| 303 | - | full := MarshalTheme(tests[0].msg) | |||
| 304 | - | assertAllPrefixesFail(t, full, UnmarshalTheme) | |||
| 305 | - | assertTrailingGarbageFails(t, full, UnmarshalTheme) | |||
| 306 | } | 286 | } | ||
| 307 | 287 | ||||
| 308 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | 288 | func TestResizeGoldenAndRoundTrip(t *testing.T) { | ||