fix(config): address PR review feedback
- commit
- 2a105ed2a2fa1bf6de4ae2d19dcd54bda4a28819
- author
- brice · · 2026-07-05 17:37 UTC
- parent
- fead047
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +96 -21
M internal/adapters/config/config.go +1 -0
| @@ -128,6 +128,7 @@ func Watch(ctx context.Context, clock ports.Clock, path string, onChange func(do | |||
| 128 | 128 | last = current | |
| 129 | 129 | cfg, warnings, err := Load(path) | |
| 130 | 130 | if err != nil { | |
| 131 | + | cfg = domain.Defaults() | |
| 131 | 132 | warnings = append(warnings, domain.Warning{Msg: err.Error()}) | |
| 132 | 133 | } | |
| 133 | 134 | onChange(cfg, warnings) | |
| @@ -128,6 +128,7 @@ func Watch(ctx context.Context, clock ports.Clock, path string, onChange func(do | |||||
| 128 | last = current | 128 | last = current | ||
| 129 | cfg, warnings, err := Load(path) | 129 | cfg, warnings, err := Load(path) | ||
| 130 | if err != nil { | 130 | if err != nil { | ||
| 131 | + | cfg = domain.Defaults() | |||
| 131 | warnings = append(warnings, domain.Warning{Msg: err.Error()}) | 132 | warnings = append(warnings, domain.Warning{Msg: err.Error()}) | ||
| 132 | } | 133 | } | ||
| 133 | onChange(cfg, warnings) | 134 | onChange(cfg, warnings) | ||
M internal/adapters/config/config_test.go +41 -0
| @@ -194,6 +194,47 @@ func TestWatchReloadsChangedFileWithFakeClock(t *testing.T) { | |||
| 194 | 194 | } | |
| 195 | 195 | } | |
| 196 | 196 | ||
| 197 | + | func TestWatchUsesDefaultsWhenReloadLoadFails(t *testing.T) { | |
| 198 | + | t.Parallel() | |
| 199 | + | ||
| 200 | + | path := filepath.Join(t.TempDir(), "config") | |
| 201 | + | if err := os.WriteFile(path, []byte("theme = dark\n"), 0o600); err != nil { | |
| 202 | + | t.Fatal(err) | |
| 203 | + | } | |
| 204 | + | ||
| 205 | + | clk := newFakeClock(time.Unix(0, 0)) | |
| 206 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 207 | + | defer cancel() | |
| 208 | + | ||
| 209 | + | type change struct { | |
| 210 | + | cfg domain.Config | |
| 211 | + | warnings []domain.Warning | |
| 212 | + | } | |
| 213 | + | changes := make(chan change, 2) | |
| 214 | + | done := make(chan error, 1) | |
| 215 | + | go func() { | |
| 216 | + | done <- Watch(ctx, clk, path, func(cfg domain.Config, warnings []domain.Warning) { | |
| 217 | + | changes <- change{cfg: cfg, warnings: warnings} | |
| 218 | + | }) | |
| 219 | + | }() | |
| 220 | + | ||
| 221 | + | clk.waitForTimers(t, 1) | |
| 222 | + | tooLong := strings.Repeat("x", 70*1024) | |
| 223 | + | if err := os.WriteFile(path, []byte(tooLong), 0o600); err != nil { | |
| 224 | + | t.Fatal(err) | |
| 225 | + | } | |
| 226 | + | clk.advance(2 * time.Second) | |
| 227 | + | ||
| 228 | + | got := <-changes | |
| 229 | + | require.Equal(t, domain.Defaults(), got.cfg) | |
| 230 | + | require.NotEmpty(t, got.warnings) | |
| 231 | + | ||
| 232 | + | cancel() | |
| 233 | + | if err := <-done; !errors.Is(err, context.Canceled) { | |
| 234 | + | t.Fatalf("Watch() error = %v, want context.Canceled", err) | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | ||
| 197 | 238 | func TestWatchDoesNotRepeatPersistentStatError(t *testing.T) { | |
| 198 | 239 | t.Parallel() | |
| 199 | 240 | ||
| @@ -194,6 +194,47 @@ func TestWatchReloadsChangedFileWithFakeClock(t *testing.T) { | |||||
| 194 | } | 194 | } | ||
| 195 | } | 195 | } | ||
| 196 | 196 | ||||
| 197 | + | func TestWatchUsesDefaultsWhenReloadLoadFails(t *testing.T) { | |||
| 198 | + | t.Parallel() | |||
| 199 | + | ||||
| 200 | + | path := filepath.Join(t.TempDir(), "config") | |||
| 201 | + | if err := os.WriteFile(path, []byte("theme = dark\n"), 0o600); err != nil { | |||
| 202 | + | t.Fatal(err) | |||
| 203 | + | } | |||
| 204 | + | ||||
| 205 | + | clk := newFakeClock(time.Unix(0, 0)) | |||
| 206 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 207 | + | defer cancel() | |||
| 208 | + | ||||
| 209 | + | type change struct { | |||
| 210 | + | cfg domain.Config | |||
| 211 | + | warnings []domain.Warning | |||
| 212 | + | } | |||
| 213 | + | changes := make(chan change, 2) | |||
| 214 | + | done := make(chan error, 1) | |||
| 215 | + | go func() { | |||
| 216 | + | done <- Watch(ctx, clk, path, func(cfg domain.Config, warnings []domain.Warning) { | |||
| 217 | + | changes <- change{cfg: cfg, warnings: warnings} | |||
| 218 | + | }) | |||
| 219 | + | }() | |||
| 220 | + | ||||
| 221 | + | clk.waitForTimers(t, 1) | |||
| 222 | + | tooLong := strings.Repeat("x", 70*1024) | |||
| 223 | + | if err := os.WriteFile(path, []byte(tooLong), 0o600); err != nil { | |||
| 224 | + | t.Fatal(err) | |||
| 225 | + | } | |||
| 226 | + | clk.advance(2 * time.Second) | |||
| 227 | + | ||||
| 228 | + | got := <-changes | |||
| 229 | + | require.Equal(t, domain.Defaults(), got.cfg) | |||
| 230 | + | require.NotEmpty(t, got.warnings) | |||
| 231 | + | ||||
| 232 | + | cancel() | |||
| 233 | + | if err := <-done; !errors.Is(err, context.Canceled) { | |||
| 234 | + | t.Fatalf("Watch() error = %v, want context.Canceled", err) | |||
| 235 | + | } | |||
| 236 | + | } | |||
| 237 | + | ||||
| 197 | func TestWatchDoesNotRepeatPersistentStatError(t *testing.T) { | 238 | func TestWatchDoesNotRepeatPersistentStatError(t *testing.T) { | ||
| 198 | t.Parallel() | 239 | t.Parallel() | ||
| 199 | 240 | ||||
M internal/usecase/theme/theme.go +4 -3
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "strings" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/vev/pkg/renderer" | |
| 9 | + | "github.com/bnema/vev/pkg/vt" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| 11 | 12 | const maxPending = 64 | |
| @@ -140,7 +141,7 @@ func (s *Scanner) Scan(data []byte, onColor func(kind int, rgb renderer.RGB), on | |||
| 140 | 141 | onBytes(data[byteStart:i]) | |
| 141 | 142 | } | |
| 142 | 143 | onScheme(light) | |
| 143 | - | i += len("\x1b[?997;1n") - 1 | |
| 144 | + | i += len(vt.ColorSchemeReportDark) - 1 | |
| 144 | 145 | byteStart = i + 1 | |
| 145 | 146 | continue | |
| 146 | 147 | } else if possible { | |
| @@ -231,8 +232,8 @@ var schemeCSIPrefixes = []struct { | |||
| 231 | 232 | seq []byte | |
| 232 | 233 | light bool | |
| 233 | 234 | }{ | |
| 234 | - | {seq: []byte("\x1b[?997;1n"), light: false}, | |
| 235 | - | {seq: []byte("\x1b[?997;2n"), light: true}, | |
| 235 | + | {seq: []byte(vt.ColorSchemeReportDark), light: false}, | |
| 236 | + | {seq: []byte(vt.ColorSchemeReportLight), light: true}, | |
| 236 | 237 | } | |
| 237 | 238 | ||
| 238 | 239 | func schemeCSINotification(data []byte) (complete bool, possible bool, light bool) { | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "strings" | 6 | "strings" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/vev/pkg/renderer" | 8 | "github.com/bnema/vev/pkg/renderer" | ||
| 9 | + | "github.com/bnema/vev/pkg/vt" | |||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| 11 | const maxPending = 64 | 12 | const maxPending = 64 | ||
| @@ -140,7 +141,7 @@ func (s *Scanner) Scan(data []byte, onColor func(kind int, rgb renderer.RGB), on | |||||
| 140 | onBytes(data[byteStart:i]) | 141 | onBytes(data[byteStart:i]) | ||
| 141 | } | 142 | } | ||
| 142 | onScheme(light) | 143 | onScheme(light) | ||
| 143 | - | i += len("\x1b[?997;1n") - 1 | 144 | + | i += len(vt.ColorSchemeReportDark) - 1 |
| 144 | byteStart = i + 1 | 145 | byteStart = i + 1 | ||
| 145 | continue | 146 | continue | ||
| 146 | } else if possible { | 147 | } else if possible { | ||
| @@ -231,8 +232,8 @@ var schemeCSIPrefixes = []struct { | |||||
| 231 | seq []byte | 232 | seq []byte | ||
| 232 | light bool | 233 | light bool | ||
| 233 | }{ | 234 | }{ | ||
| 234 | - | {seq: []byte("\x1b[?997;1n"), light: false}, | 235 | + | {seq: []byte(vt.ColorSchemeReportDark), light: false}, |
| 235 | - | {seq: []byte("\x1b[?997;2n"), light: true}, | 236 | + | {seq: []byte(vt.ColorSchemeReportLight), light: true}, |
| 236 | } | 237 | } | ||
| 237 | 238 | ||||
| 238 | func schemeCSINotification(data []byte) (complete bool, possible bool, light bool) { | 239 | func schemeCSINotification(data []byte) (complete bool, possible bool, light bool) { | ||
M internal/usecase/theme/theme_test.go +36 -10
| @@ -2,6 +2,7 @@ package theme | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | + | "reflect" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| 7 | 8 | "github.com/bnema/vev/pkg/renderer" | |
| @@ -83,18 +84,43 @@ func TestScannerHandlesSplitSequences(t *testing.T) { | |||
| 83 | 84 | } | |
| 84 | 85 | ||
| 85 | 86 | func TestScannerHandlesSplitSchemeNotifications(t *testing.T) { | |
| 86 | - | var s Scanner | |
| 87 | - | var schemes []bool | |
| 88 | - | var out bytes.Buffer | |
| 87 | + | tests := []struct { | |
| 88 | + | name string | |
| 89 | + | chunks []string | |
| 90 | + | wantOut string | |
| 91 | + | wantSchemes []bool | |
| 92 | + | }{ | |
| 93 | + | { | |
| 94 | + | name: "split dark then complete light", | |
| 95 | + | chunks: []string{"a\x1b[?997", ";1nb\x1b[?997;2nc"}, | |
| 96 | + | wantOut: "abc", | |
| 97 | + | wantSchemes: []bool{false, true}, | |
| 98 | + | }, | |
| 99 | + | { | |
| 100 | + | name: "split light", | |
| 101 | + | chunks: []string{"x\x1b[?997;", "2ny"}, | |
| 102 | + | wantOut: "xy", | |
| 103 | + | wantSchemes: []bool{true}, | |
| 104 | + | }, | |
| 105 | + | } | |
| 89 | 106 | ||
| 90 | - | s.Scan([]byte("a\x1b[?997"), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) | |
| 91 | - | s.Scan([]byte(";1nb\x1b[?997;2nc"), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) | |
| 107 | + | for _, tt := range tests { | |
| 108 | + | t.Run(tt.name, func(t *testing.T) { | |
| 109 | + | var s Scanner | |
| 110 | + | var schemes []bool | |
| 111 | + | var out bytes.Buffer | |
| 92 | 112 | ||
| 93 | - | if out.String() != "abc" { | |
| 94 | - | t.Fatalf("passthrough=%q want abc", out.String()) | |
| 95 | - | } | |
| 96 | - | if len(schemes) != 2 || schemes[0] != false || schemes[1] != true { | |
| 97 | - | t.Fatalf("schemes=%v want [false true]", schemes) | |
| 113 | + | for _, chunk := range tt.chunks { | |
| 114 | + | s.Scan([]byte(chunk), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | if out.String() != tt.wantOut { | |
| 118 | + | t.Fatalf("passthrough=%q want %q", out.String(), tt.wantOut) | |
| 119 | + | } | |
| 120 | + | if !reflect.DeepEqual(schemes, tt.wantSchemes) { | |
| 121 | + | t.Fatalf("schemes=%v want %v", schemes, tt.wantSchemes) | |
| 122 | + | } | |
| 123 | + | }) | |
| 98 | 124 | } | |
| 99 | 125 | } | |
| 100 | 126 | ||
| @@ -2,6 +2,7 @@ package theme | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | + | "reflect" | |||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| 7 | "github.com/bnema/vev/pkg/renderer" | 8 | "github.com/bnema/vev/pkg/renderer" | ||
| @@ -83,18 +84,43 @@ func TestScannerHandlesSplitSequences(t *testing.T) { | |||||
| 83 | } | 84 | } | ||
| 84 | 85 | ||||
| 85 | func TestScannerHandlesSplitSchemeNotifications(t *testing.T) { | 86 | func TestScannerHandlesSplitSchemeNotifications(t *testing.T) { | ||
| 86 | - | var s Scanner | 87 | + | tests := []struct { |
| 87 | - | var schemes []bool | 88 | + | name string |
| 88 | - | var out bytes.Buffer | 89 | + | chunks []string |
| 90 | + | wantOut string | |||
| 91 | + | wantSchemes []bool | |||
| 92 | + | }{ | |||
| 93 | + | { | |||
| 94 | + | name: "split dark then complete light", | |||
| 95 | + | chunks: []string{"a\x1b[?997", ";1nb\x1b[?997;2nc"}, | |||
| 96 | + | wantOut: "abc", | |||
| 97 | + | wantSchemes: []bool{false, true}, | |||
| 98 | + | }, | |||
| 99 | + | { | |||
| 100 | + | name: "split light", | |||
| 101 | + | chunks: []string{"x\x1b[?997;", "2ny"}, | |||
| 102 | + | wantOut: "xy", | |||
| 103 | + | wantSchemes: []bool{true}, | |||
| 104 | + | }, | |||
| 105 | + | } | |||
| 89 | 106 | ||||
| 90 | - | s.Scan([]byte("a\x1b[?997"), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) | 107 | + | for _, tt := range tests { |
| 91 | - | s.Scan([]byte(";1nb\x1b[?997;2nc"), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) | 108 | + | t.Run(tt.name, func(t *testing.T) { |
| 109 | + | var s Scanner | |||
| 110 | + | var schemes []bool | |||
| 111 | + | var out bytes.Buffer | |||
| 92 | 112 | ||||
| 93 | - | if out.String() != "abc" { | 113 | + | for _, chunk := range tt.chunks { |
| 94 | - | t.Fatalf("passthrough=%q want abc", out.String()) | 114 | + | s.Scan([]byte(chunk), func(kind int, rgb renderer.RGB) {}, func(light bool) { schemes = append(schemes, light) }, func(b []byte) { out.Write(b) }) |
| 95 | - | } | 115 | + | } |
| 96 | - | if len(schemes) != 2 || schemes[0] != false || schemes[1] != true { | 116 | + | |
| 97 | - | t.Fatalf("schemes=%v want [false true]", schemes) | 117 | + | if out.String() != tt.wantOut { |
| 118 | + | t.Fatalf("passthrough=%q want %q", out.String(), tt.wantOut) | |||
| 119 | + | } | |||
| 120 | + | if !reflect.DeepEqual(schemes, tt.wantSchemes) { | |||
| 121 | + | t.Fatalf("schemes=%v want %v", schemes, tt.wantSchemes) | |||
| 122 | + | } | |||
| 123 | + | }) | |||
| 98 | } | 124 | } | ||
| 99 | } | 125 | } | ||
| 100 | 126 | ||||
M pkg/vt/query_test.go +3 -3
| @@ -25,8 +25,8 @@ func TestScreenQueryResponses(t *testing.T) { | |||
| 25 | 25 | {name: "DECRQM 2026 set", input: "\x1b[?2026h\x1b[?2026$p", want: "\x1b[?2026;1$y"}, | |
| 26 | 26 | {name: "DECRQM 2031 reset", input: "\x1b[?2031$p", want: "\x1b[?2031;2$y"}, | |
| 27 | 27 | {name: "DECRQM 2031 set", input: "\x1b[?2031h\x1b[?2031$p", want: "\x1b[?2031;1$y"}, | |
| 28 | - | {name: "color scheme DSR dark", input: "\x1b[?996n", want: "\x1b[?997;1n"}, | |
| 29 | - | {name: "color scheme DSR light", input: "\x1b[?996n", want: "\x1b[?997;2n"}, | |
| 28 | + | {name: "color scheme DSR dark", input: "\x1b[?996n", want: ColorSchemeReportDark}, | |
| 29 | + | {name: "color scheme DSR light", input: "\x1b[?996n", want: ColorSchemeReportLight}, | |
| 30 | 30 | {name: "DECRQM unknown mode", input: "\x1b[?1337$p", want: "\x1b[?1337;0$y"}, | |
| 31 | 31 | {name: "kitty keyboard query unanswered", input: "\x1b[?u", want: ""}, | |
| 32 | 32 | {name: "XTVERSION unanswered", input: "\x1b[>0q", want: ""}, | |
| @@ -190,7 +190,7 @@ func TestScreenColorSchemeUnsolicitedOnlySubscribedAndChanged(t *testing.T) { | |||
| 190 | 190 | t.Fatalf("unchanged response = %q, want silence", got.String()) | |
| 191 | 191 | } | |
| 192 | 192 | s.SetColorScheme(false) | |
| 193 | - | if got.String() != "\x1b[?997;1n" { | |
| 193 | + | if got.String() != ColorSchemeReportDark { | |
| 194 | 194 | t.Fatalf("changed response = %q, want dark notification", got.String()) | |
| 195 | 195 | } | |
| 196 | 196 | got.Reset() | |
| @@ -25,8 +25,8 @@ func TestScreenQueryResponses(t *testing.T) { | |||||
| 25 | {name: "DECRQM 2026 set", input: "\x1b[?2026h\x1b[?2026$p", want: "\x1b[?2026;1$y"}, | 25 | {name: "DECRQM 2026 set", input: "\x1b[?2026h\x1b[?2026$p", want: "\x1b[?2026;1$y"}, | ||
| 26 | {name: "DECRQM 2031 reset", input: "\x1b[?2031$p", want: "\x1b[?2031;2$y"}, | 26 | {name: "DECRQM 2031 reset", input: "\x1b[?2031$p", want: "\x1b[?2031;2$y"}, | ||
| 27 | {name: "DECRQM 2031 set", input: "\x1b[?2031h\x1b[?2031$p", want: "\x1b[?2031;1$y"}, | 27 | {name: "DECRQM 2031 set", input: "\x1b[?2031h\x1b[?2031$p", want: "\x1b[?2031;1$y"}, | ||
| 28 | - | {name: "color scheme DSR dark", input: "\x1b[?996n", want: "\x1b[?997;1n"}, | 28 | + | {name: "color scheme DSR dark", input: "\x1b[?996n", want: ColorSchemeReportDark}, |
| 29 | - | {name: "color scheme DSR light", input: "\x1b[?996n", want: "\x1b[?997;2n"}, | 29 | + | {name: "color scheme DSR light", input: "\x1b[?996n", want: ColorSchemeReportLight}, |
| 30 | {name: "DECRQM unknown mode", input: "\x1b[?1337$p", want: "\x1b[?1337;0$y"}, | 30 | {name: "DECRQM unknown mode", input: "\x1b[?1337$p", want: "\x1b[?1337;0$y"}, | ||
| 31 | {name: "kitty keyboard query unanswered", input: "\x1b[?u", want: ""}, | 31 | {name: "kitty keyboard query unanswered", input: "\x1b[?u", want: ""}, | ||
| 32 | {name: "XTVERSION unanswered", input: "\x1b[>0q", want: ""}, | 32 | {name: "XTVERSION unanswered", input: "\x1b[>0q", want: ""}, | ||
| @@ -190,7 +190,7 @@ func TestScreenColorSchemeUnsolicitedOnlySubscribedAndChanged(t *testing.T) { | |||||
| 190 | t.Fatalf("unchanged response = %q, want silence", got.String()) | 190 | t.Fatalf("unchanged response = %q, want silence", got.String()) | ||
| 191 | } | 191 | } | ||
| 192 | s.SetColorScheme(false) | 192 | s.SetColorScheme(false) | ||
| 193 | - | if got.String() != "\x1b[?997;1n" { | 193 | + | if got.String() != ColorSchemeReportDark { |
| 194 | t.Fatalf("changed response = %q, want dark notification", got.String()) | 194 | t.Fatalf("changed response = %q, want dark notification", got.String()) | ||
| 195 | } | 195 | } | ||
| 196 | got.Reset() | 196 | got.Reset() | ||
M pkg/vt/screen.go +11 -5
| @@ -31,6 +31,13 @@ type screenState struct { | |||
| 31 | 31 | // so keep this value in sync if that payload cap changes. | |
| 32 | 32 | const maxEscapeBufferLen = 128 * 1024 | |
| 33 | 33 | ||
| 34 | + | const ( | |
| 35 | + | // ColorSchemeReportDark is the DEC 2031 dark-scheme report. | |
| 36 | + | ColorSchemeReportDark = "\x1b[?997;1n" | |
| 37 | + | // ColorSchemeReportLight is the DEC 2031 light-scheme report. | |
| 38 | + | ColorSchemeReportLight = "\x1b[?997;2n" | |
| 39 | + | ) | |
| 40 | + | ||
| 34 | 41 | const ( | |
| 35 | 42 | progressStateClear = iota | |
| 36 | 43 | progressStateNormal | |
| @@ -509,9 +516,9 @@ func (s *Screen) colorSchemeReport() []byte { | |||
| 509 | 516 | return nil | |
| 510 | 517 | } | |
| 511 | 518 | if s.colorSchemeLight { | |
| 512 | - | return []byte("\x1b[?997;2n") | |
| 519 | + | return []byte(ColorSchemeReportLight) | |
| 513 | 520 | } | |
| 514 | - | return []byte("\x1b[?997;1n") | |
| 521 | + | return []byte(ColorSchemeReportDark) | |
| 515 | 522 | } | |
| 516 | 523 | ||
| 517 | 524 | func (s *Screen) scrollDownRegion(top, bottom, n int) { | |
| @@ -754,11 +761,10 @@ func (s *Screen) handleProgress(rest string) { | |||
| 754 | 761 | // clipboard query (data == "?") is always ignored — vev never answers | |
| 755 | 762 | // clipboard queries. A payload with no second ";" is malformed and ignored. | |
| 756 | 763 | func (s *Screen) handleOSC52(rest string) { | |
| 757 | - | idx := strings.IndexByte(rest, ';') | |
| 758 | - | if idx < 0 { | |
| 764 | + | _, data, ok := strings.Cut(rest, ";") | |
| 765 | + | if !ok { | |
| 759 | 766 | return | |
| 760 | 767 | } | |
| 761 | - | data := rest[idx+1:] | |
| 762 | 768 | if data == "?" { | |
| 763 | 769 | return | |
| 764 | 770 | } | |
| @@ -31,6 +31,13 @@ type screenState struct { | |||||
| 31 | // so keep this value in sync if that payload cap changes. | 31 | // so keep this value in sync if that payload cap changes. | ||
| 32 | const maxEscapeBufferLen = 128 * 1024 | 32 | const maxEscapeBufferLen = 128 * 1024 | ||
| 33 | 33 | ||||
| 34 | + | const ( | |||
| 35 | + | // ColorSchemeReportDark is the DEC 2031 dark-scheme report. | |||
| 36 | + | ColorSchemeReportDark = "\x1b[?997;1n" | |||
| 37 | + | // ColorSchemeReportLight is the DEC 2031 light-scheme report. | |||
| 38 | + | ColorSchemeReportLight = "\x1b[?997;2n" | |||
| 39 | + | ) | |||
| 40 | + | ||||
| 34 | const ( | 41 | const ( | ||
| 35 | progressStateClear = iota | 42 | progressStateClear = iota | ||
| 36 | progressStateNormal | 43 | progressStateNormal | ||
| @@ -509,9 +516,9 @@ func (s *Screen) colorSchemeReport() []byte { | |||||
| 509 | return nil | 516 | return nil | ||
| 510 | } | 517 | } | ||
| 511 | if s.colorSchemeLight { | 518 | if s.colorSchemeLight { | ||
| 512 | - | return []byte("\x1b[?997;2n") | 519 | + | return []byte(ColorSchemeReportLight) |
| 513 | } | 520 | } | ||
| 514 | - | return []byte("\x1b[?997;1n") | 521 | + | return []byte(ColorSchemeReportDark) |
| 515 | } | 522 | } | ||
| 516 | 523 | ||||
| 517 | func (s *Screen) scrollDownRegion(top, bottom, n int) { | 524 | func (s *Screen) scrollDownRegion(top, bottom, n int) { | ||
| @@ -754,11 +761,10 @@ func (s *Screen) handleProgress(rest string) { | |||||
| 754 | // clipboard query (data == "?") is always ignored — vev never answers | 761 | // clipboard query (data == "?") is always ignored — vev never answers | ||
| 755 | // clipboard queries. A payload with no second ";" is malformed and ignored. | 762 | // clipboard queries. A payload with no second ";" is malformed and ignored. | ||
| 756 | func (s *Screen) handleOSC52(rest string) { | 763 | func (s *Screen) handleOSC52(rest string) { | ||
| 757 | - | idx := strings.IndexByte(rest, ';') | 764 | + | _, data, ok := strings.Cut(rest, ";") |
| 758 | - | if idx < 0 { | 765 | + | if !ok { |
| 759 | return | 766 | return | ||
| 760 | } | 767 | } | ||
| 761 | - | data := rest[idx+1:] | |||
| 762 | if data == "?" { | 768 | if data == "?" { | ||
| 763 | return | 769 | return | ||
| 764 | } | 770 | } | ||