feat(vt): expose live grid line bounds
- commit
- 5be247520ca69342fa64de1b3d5c9066301b963e
- author
- brice · · 2026-07-25 06:11 UTC
- parent
- ff84b77
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +34 -0
M pkg/vt/screen.go +9 -0
| @@ -111,6 +111,15 @@ func NewScreenWithHistory(width, height int, config HistoryConfig) *Screen { | |||
| 111 | 111 | // configured with NewScreenWithHistory. | |
| 112 | 112 | func (s *Screen) History() *History { return s.history } | |
| 113 | 113 | ||
| 114 | + | // LineBounds returns an owned copy of the live grid's per-row logical extents, | |
| 115 | + | // indexed like Frame rows. It returns nil when the screen has no buffer. | |
| 116 | + | func (s *Screen) LineBounds() []LineBound { | |
| 117 | + | if s == nil || s.buffer == nil { | |
| 118 | + | return nil | |
| 119 | + | } | |
| 120 | + | return append([]LineBound(nil), s.buffer.boundaries...) | |
| 121 | + | } | |
| 122 | + | ||
| 114 | 123 | func (s *Screen) Write(data []byte) { | |
| 115 | 124 | if len(s.escapeBuf) > 0 { | |
| 116 | 125 | combined := make([]byte, 0, len(s.escapeBuf)+len(data)) | |
| @@ -111,6 +111,15 @@ func NewScreenWithHistory(width, height int, config HistoryConfig) *Screen { | |||||
| 111 | // configured with NewScreenWithHistory. | 111 | // configured with NewScreenWithHistory. | ||
| 112 | func (s *Screen) History() *History { return s.history } | 112 | func (s *Screen) History() *History { return s.history } | ||
| 113 | 113 | ||||
| 114 | + | // LineBounds returns an owned copy of the live grid's per-row logical extents, | |||
| 115 | + | // indexed like Frame rows. It returns nil when the screen has no buffer. | |||
| 116 | + | func (s *Screen) LineBounds() []LineBound { | |||
| 117 | + | if s == nil || s.buffer == nil { | |||
| 118 | + | return nil | |||
| 119 | + | } | |||
| 120 | + | return append([]LineBound(nil), s.buffer.boundaries...) | |||
| 121 | + | } | |||
| 122 | + | ||||
| 114 | func (s *Screen) Write(data []byte) { | 123 | func (s *Screen) Write(data []byte) { | ||
| 115 | if len(s.escapeBuf) > 0 { | 124 | if len(s.escapeBuf) > 0 { | ||
| 116 | combined := make([]byte, 0, len(s.escapeBuf)+len(data)) | 125 | combined := make([]byte, 0, len(s.escapeBuf)+len(data)) | ||
M pkg/vt/screen_write_test.go +25 -0
| @@ -922,3 +922,28 @@ func TestC1Controls(t *testing.T) { | |||
| 922 | 922 | t.Run(tt.name, tt.run) | |
| 923 | 923 | } | |
| 924 | 924 | } | |
| 925 | + | ||
| 926 | + | func TestScreenLineBoundsDescribesTheLiveGrid(t *testing.T) { | |
| 927 | + | s := NewScreen(4, 3) | |
| 928 | + | s.Write([]byte("abcdef")) // wraps after column 4 | |
| 929 | + | ||
| 930 | + | bounds := s.LineBounds() | |
| 931 | + | if len(bounds) != 3 { | |
| 932 | + | t.Fatalf("len(LineBounds()) = %d, want 3", len(bounds)) | |
| 933 | + | } | |
| 934 | + | if !bounds[0].Soft { | |
| 935 | + | t.Errorf("bounds[0].Soft = false, want true (row wrapped)") | |
| 936 | + | } | |
| 937 | + | if bounds[1].Soft { | |
| 938 | + | t.Errorf("bounds[1].Soft = true, want false (row did not wrap)") | |
| 939 | + | } | |
| 940 | + | if bounds[1].End != 2 { | |
| 941 | + | t.Errorf("bounds[1].End = %d, want 2", bounds[1].End) | |
| 942 | + | } | |
| 943 | + | ||
| 944 | + | // The result is owned by the caller. | |
| 945 | + | bounds[0] = LineBound{} | |
| 946 | + | if !s.LineBounds()[0].Soft { | |
| 947 | + | t.Error("mutating the result changed the screen's own boundaries") | |
| 948 | + | } | |
| 949 | + | } | |
| @@ -922,3 +922,28 @@ func TestC1Controls(t *testing.T) { | |||||
| 922 | t.Run(tt.name, tt.run) | 922 | t.Run(tt.name, tt.run) | ||
| 923 | } | 923 | } | ||
| 924 | } | 924 | } | ||
| 925 | + | ||||
| 926 | + | func TestScreenLineBoundsDescribesTheLiveGrid(t *testing.T) { | |||
| 927 | + | s := NewScreen(4, 3) | |||
| 928 | + | s.Write([]byte("abcdef")) // wraps after column 4 | |||
| 929 | + | ||||
| 930 | + | bounds := s.LineBounds() | |||
| 931 | + | if len(bounds) != 3 { | |||
| 932 | + | t.Fatalf("len(LineBounds()) = %d, want 3", len(bounds)) | |||
| 933 | + | } | |||
| 934 | + | if !bounds[0].Soft { | |||
| 935 | + | t.Errorf("bounds[0].Soft = false, want true (row wrapped)") | |||
| 936 | + | } | |||
| 937 | + | if bounds[1].Soft { | |||
| 938 | + | t.Errorf("bounds[1].Soft = true, want false (row did not wrap)") | |||
| 939 | + | } | |||
| 940 | + | if bounds[1].End != 2 { | |||
| 941 | + | t.Errorf("bounds[1].End = %d, want 2", bounds[1].End) | |||
| 942 | + | } | |||
| 943 | + | ||||
| 944 | + | // The result is owned by the caller. | |||
| 945 | + | bounds[0] = LineBound{} | |||
| 946 | + | if !s.LineBounds()[0].Soft { | |||
| 947 | + | t.Error("mutating the result changed the screen's own boundaries") | |||
| 948 | + | } | |||
| 949 | + | } | |||