Merge pull request #62 from bnema/agent/fix-bce-erase
Fix background color erase semantics
2 files · +77 -2
M pkg/vt/screen.go +15 -2
| @@ -394,6 +394,17 @@ func (s *Screen) clearWidePairAt(x, y int) { | |||
| 394 | 394 | } | |
| 395 | 395 | } | |
| 396 | 396 | ||
| 397 | + | func (s *Screen) eraseCell() renderer.Cell { | |
| 398 | + | style := renderer.DefaultStyle() | |
| 399 | + | if s.Style.HasBackgroundRGB { | |
| 400 | + | style.HasBackgroundRGB = true | |
| 401 | + | style.BackgroundRGB = s.Style.BackgroundRGB | |
| 402 | + | } else { | |
| 403 | + | style.Background = s.Style.Background | |
| 404 | + | } | |
| 405 | + | return renderer.Cell{Rune: ' ', Style: style} | |
| 406 | + | } | |
| 407 | + | ||
| 397 | 408 | // clearRow blanks cells [x0,x1) on row y, extending the range to swallow either | |
| 398 | 409 | // half of a wide pair that straddles a boundary so no orphan half is left. It | |
| 399 | 410 | // returns the actual modified span [start, start+width). | |
| @@ -419,8 +430,9 @@ func (s *Screen) clearRow(y, x0, x1 int) (start, width int) { | |||
| 419 | 430 | if x1 < s.Frame.Width && s.Frame.At(x1, y).Continuation { | |
| 420 | 431 | x1++ | |
| 421 | 432 | } | |
| 433 | + | blank := s.eraseCell() | |
| 422 | 434 | for x := x0; x < x1; x++ { | |
| 423 | - | s.Frame.Set(x, y, renderer.BlankCell()) | |
| 435 | + | s.Frame.Set(x, y, blank) | |
| 424 | 436 | } | |
| 425 | 437 | return x0, x1 - x0 | |
| 426 | 438 | } | |
| @@ -1448,8 +1460,9 @@ func (s *Screen) clearScreenMode(mode int) { | |||
| 1448 | 1460 | } | |
| 1449 | 1461 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: min(s.Row+1, s.Frame.Height), Count: 1}) | |
| 1450 | 1462 | case 2, 3: | |
| 1463 | + | blank := s.eraseCell() | |
| 1451 | 1464 | for i := range s.Frame.Cells { | |
| 1452 | - | s.Frame.Cells[i] = renderer.BlankCell() | |
| 1465 | + | s.Frame.Cells[i] = blank | |
| 1453 | 1466 | } | |
| 1454 | 1467 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: s.Frame.Height, Count: 1}) | |
| 1455 | 1468 | default: | |
| @@ -394,6 +394,17 @@ func (s *Screen) clearWidePairAt(x, y int) { | |||||
| 394 | } | 394 | } | ||
| 395 | } | 395 | } | ||
| 396 | 396 | ||||
| 397 | + | func (s *Screen) eraseCell() renderer.Cell { | |||
| 398 | + | style := renderer.DefaultStyle() | |||
| 399 | + | if s.Style.HasBackgroundRGB { | |||
| 400 | + | style.HasBackgroundRGB = true | |||
| 401 | + | style.BackgroundRGB = s.Style.BackgroundRGB | |||
| 402 | + | } else { | |||
| 403 | + | style.Background = s.Style.Background | |||
| 404 | + | } | |||
| 405 | + | return renderer.Cell{Rune: ' ', Style: style} | |||
| 406 | + | } | |||
| 407 | + | ||||
| 397 | // clearRow blanks cells [x0,x1) on row y, extending the range to swallow either | 408 | // clearRow blanks cells [x0,x1) on row y, extending the range to swallow either | ||
| 398 | // half of a wide pair that straddles a boundary so no orphan half is left. It | 409 | // half of a wide pair that straddles a boundary so no orphan half is left. It | ||
| 399 | // returns the actual modified span [start, start+width). | 410 | // returns the actual modified span [start, start+width). | ||
| @@ -419,8 +430,9 @@ func (s *Screen) clearRow(y, x0, x1 int) (start, width int) { | |||||
| 419 | if x1 < s.Frame.Width && s.Frame.At(x1, y).Continuation { | 430 | if x1 < s.Frame.Width && s.Frame.At(x1, y).Continuation { | ||
| 420 | x1++ | 431 | x1++ | ||
| 421 | } | 432 | } | ||
| 433 | + | blank := s.eraseCell() | |||
| 422 | for x := x0; x < x1; x++ { | 434 | for x := x0; x < x1; x++ { | ||
| 423 | - | s.Frame.Set(x, y, renderer.BlankCell()) | 435 | + | s.Frame.Set(x, y, blank) |
| 424 | } | 436 | } | ||
| 425 | return x0, x1 - x0 | 437 | return x0, x1 - x0 | ||
| 426 | } | 438 | } | ||
| @@ -1448,8 +1460,9 @@ func (s *Screen) clearScreenMode(mode int) { | |||||
| 1448 | } | 1460 | } | ||
| 1449 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: min(s.Row+1, s.Frame.Height), Count: 1}) | 1461 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: min(s.Row+1, s.Frame.Height), Count: 1}) | ||
| 1450 | case 2, 3: | 1462 | case 2, 3: | ||
| 1463 | + | blank := s.eraseCell() | |||
| 1451 | for i := range s.Frame.Cells { | 1464 | for i := range s.Frame.Cells { | ||
| 1452 | - | s.Frame.Cells[i] = renderer.BlankCell() | 1465 | + | s.Frame.Cells[i] = blank |
| 1453 | } | 1466 | } | ||
| 1454 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: s.Frame.Height, Count: 1}) | 1467 | s.record(renderer.Damage{Kind: renderer.DamageClear, X: 0, Y: 0, Width: s.Frame.Width, Height: s.Frame.Height, Count: 1}) | ||
| 1455 | default: | 1468 | default: | ||
M pkg/vt/screen_test.go +62 -0
| @@ -1177,6 +1177,68 @@ func TestClearAndErase(t *testing.T) { | |||
| 1177 | 1177 | } | |
| 1178 | 1178 | } | |
| 1179 | 1179 | ||
| 1180 | + | func TestBackgroundColorErase(t *testing.T) { | |
| 1181 | + | tests := []struct { | |
| 1182 | + | name string | |
| 1183 | + | width int | |
| 1184 | + | height int | |
| 1185 | + | seq string | |
| 1186 | + | startX int | |
| 1187 | + | wantStyle renderer.Style | |
| 1188 | + | }{ | |
| 1189 | + | { | |
| 1190 | + | name: "indexed background for EL", | |
| 1191 | + | width: 8, | |
| 1192 | + | height: 1, | |
| 1193 | + | seq: "\x1b[1;31;48;5;236mX\x1b[K", | |
| 1194 | + | startX: 1, | |
| 1195 | + | wantStyle: renderer.Style{Foreground: -1, Background: 236}, | |
| 1196 | + | }, | |
| 1197 | + | { | |
| 1198 | + | name: "truecolor background for full ED", | |
| 1199 | + | width: 3, | |
| 1200 | + | height: 2, | |
| 1201 | + | seq: "abcdef\x1b[1;31;48;2;12;34;56m\x1b[2J", | |
| 1202 | + | wantStyle: renderer.Style{ | |
| 1203 | + | Foreground: -1, | |
| 1204 | + | Background: -1, | |
| 1205 | + | HasBackgroundRGB: true, | |
| 1206 | + | BackgroundRGB: renderer.RGB{R: 12, G: 34, B: 56}, | |
| 1207 | + | }, | |
| 1208 | + | }, | |
| 1209 | + | { | |
| 1210 | + | name: "default background after SGR 49", | |
| 1211 | + | width: 2, | |
| 1212 | + | height: 1, | |
| 1213 | + | seq: "\x1b[48;2;12;34;56m\x1b[49m\x1b[K", | |
| 1214 | + | wantStyle: renderer.DefaultStyle(), | |
| 1215 | + | }, | |
| 1216 | + | { | |
| 1217 | + | name: "default background after SGR 0", | |
| 1218 | + | width: 2, | |
| 1219 | + | height: 1, | |
| 1220 | + | seq: "\x1b[48;2;12;34;56m\x1b[0m\x1b[K", | |
| 1221 | + | wantStyle: renderer.DefaultStyle(), | |
| 1222 | + | }, | |
| 1223 | + | } | |
| 1224 | + | ||
| 1225 | + | for _, tt := range tests { | |
| 1226 | + | t.Run(tt.name, func(t *testing.T) { | |
| 1227 | + | s := NewScreen(tt.width, tt.height) | |
| 1228 | + | s.Write([]byte(tt.seq)) | |
| 1229 | + | ||
| 1230 | + | for y := range tt.height { | |
| 1231 | + | for x := tt.startX; x < tt.width; x++ { | |
| 1232 | + | cell := cellAt(s, x, y) | |
| 1233 | + | if cell.Rune != ' ' || cell.Style != tt.wantStyle { | |
| 1234 | + | t.Errorf("cell(%d,%d) = %+v, want space with style %+v", x, y, cell, tt.wantStyle) | |
| 1235 | + | } | |
| 1236 | + | } | |
| 1237 | + | } | |
| 1238 | + | }) | |
| 1239 | + | } | |
| 1240 | + | } | |
| 1241 | + | ||
| 1180 | 1242 | // --------------------------------------------------------------------------- | |
| 1181 | 1243 | // Cursor move | |
| 1182 | 1244 | // --------------------------------------------------------------------------- | |
| @@ -1177,6 +1177,68 @@ func TestClearAndErase(t *testing.T) { | |||||
| 1177 | } | 1177 | } | ||
| 1178 | } | 1178 | } | ||
| 1179 | 1179 | ||||
| 1180 | + | func TestBackgroundColorErase(t *testing.T) { | |||
| 1181 | + | tests := []struct { | |||
| 1182 | + | name string | |||
| 1183 | + | width int | |||
| 1184 | + | height int | |||
| 1185 | + | seq string | |||
| 1186 | + | startX int | |||
| 1187 | + | wantStyle renderer.Style | |||
| 1188 | + | }{ | |||
| 1189 | + | { | |||
| 1190 | + | name: "indexed background for EL", | |||
| 1191 | + | width: 8, | |||
| 1192 | + | height: 1, | |||
| 1193 | + | seq: "\x1b[1;31;48;5;236mX\x1b[K", | |||
| 1194 | + | startX: 1, | |||
| 1195 | + | wantStyle: renderer.Style{Foreground: -1, Background: 236}, | |||
| 1196 | + | }, | |||
| 1197 | + | { | |||
| 1198 | + | name: "truecolor background for full ED", | |||
| 1199 | + | width: 3, | |||
| 1200 | + | height: 2, | |||
| 1201 | + | seq: "abcdef\x1b[1;31;48;2;12;34;56m\x1b[2J", | |||
| 1202 | + | wantStyle: renderer.Style{ | |||
| 1203 | + | Foreground: -1, | |||
| 1204 | + | Background: -1, | |||
| 1205 | + | HasBackgroundRGB: true, | |||
| 1206 | + | BackgroundRGB: renderer.RGB{R: 12, G: 34, B: 56}, | |||
| 1207 | + | }, | |||
| 1208 | + | }, | |||
| 1209 | + | { | |||
| 1210 | + | name: "default background after SGR 49", | |||
| 1211 | + | width: 2, | |||
| 1212 | + | height: 1, | |||
| 1213 | + | seq: "\x1b[48;2;12;34;56m\x1b[49m\x1b[K", | |||
| 1214 | + | wantStyle: renderer.DefaultStyle(), | |||
| 1215 | + | }, | |||
| 1216 | + | { | |||
| 1217 | + | name: "default background after SGR 0", | |||
| 1218 | + | width: 2, | |||
| 1219 | + | height: 1, | |||
| 1220 | + | seq: "\x1b[48;2;12;34;56m\x1b[0m\x1b[K", | |||
| 1221 | + | wantStyle: renderer.DefaultStyle(), | |||
| 1222 | + | }, | |||
| 1223 | + | } | |||
| 1224 | + | ||||
| 1225 | + | for _, tt := range tests { | |||
| 1226 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 1227 | + | s := NewScreen(tt.width, tt.height) | |||
| 1228 | + | s.Write([]byte(tt.seq)) | |||
| 1229 | + | ||||
| 1230 | + | for y := range tt.height { | |||
| 1231 | + | for x := tt.startX; x < tt.width; x++ { | |||
| 1232 | + | cell := cellAt(s, x, y) | |||
| 1233 | + | if cell.Rune != ' ' || cell.Style != tt.wantStyle { | |||
| 1234 | + | t.Errorf("cell(%d,%d) = %+v, want space with style %+v", x, y, cell, tt.wantStyle) | |||
| 1235 | + | } | |||
| 1236 | + | } | |||
| 1237 | + | } | |||
| 1238 | + | }) | |||
| 1239 | + | } | |||
| 1240 | + | } | |||
| 1241 | + | ||||
| 1180 | // --------------------------------------------------------------------------- | 1242 | // --------------------------------------------------------------------------- | ||
| 1181 | // Cursor move | 1243 | // Cursor move | ||
| 1182 | // --------------------------------------------------------------------------- | 1244 | // --------------------------------------------------------------------------- | ||