AtlatestRepositorysigil-vt
1
;;; Conformance suite for (sigil vt) — the native VT core.2
;;;3
;;; A faithful port of slate's test/term-test.sgl (the emulator's behavior IS4
;;; the spec). Each fixture feeds a byte/escape sequence into a fresh emulator5
;;; and asserts the resulting grid text, cursor, attrs, or mode flags. Covers6
;;; the MUST tier: C0, CSI cursor/erase/insert/delete/scroll-region, SGR7
;;; 16/256/truecolor (+ colon sub-params), alt-screen, autowrap (incl. the8
;;; deferred-wrap edge), tabs, DECSC/DECRC, origin mode, DECALN, OSC title,9
;;; DECSCUSR, bracketed paste, DSR/DA replies, scrollback, resize, incremental10
;;; UTF-8 across chunk boundaries, the ground-state bulk-run fast path, the11
;;; trust boundary (hostile input), plus the native additions (events, mouse12
;;; flags, row-runs).14
(import (sigil test)15
(sigil vt))17
;; ---- helpers --------------------------------------------------------------18
(define ESC "\x1b;")19
(define (csi . parts) (apply string-append ESC "[" parts))21
(define (vt* cols rows . feeds)22
(let ((t (vt-make cols rows)))23
(for-each (lambda (s) (vt-feed! t s)) feeds)24
t))26
(define (rtrim s)27
(let loop ((i (string-length s)))28
(cond ((= i 0) "")29
((char=? (string-ref s (- i 1)) #\space) (loop (- i 1)))30
(else (substring s 0 i)))))31
(define (rows-of t)32
(let loop ((i (- (vt-rows t) 1)) (acc '()))33
(if (< i 0) acc (loop (- i 1) (cons (rtrim (vt-row-text t i)) acc)))))34
(define (cursor-of t) (list (vt-cursor-row t) (vt-cursor-col t)))35
(define (cell-at t row col)36
(let ((c (vector-ref (vt-row-cells t row) col)))37
(list (string (integer->char (vt-cell-ch c)))38
(vt-cell-attr c) (vt-cell-fg c) (vt-cell-bg c))))40
;; ==========================================================================41
;; plain text, C0, wrapping42
;; ==========================================================================43
(test-group "print / C0 / wrap"44
(test "print: text lands on row 0"45
(let ((t (vt* 10 3 "hello")))46
(assert-equal (list "hello" "" "") (rows-of t))47
(assert-equal (list 0 5) (cursor-of t))))48
(test "CRLF: second line"49
(let ((t (vt* 10 3 "ab\r\ncd")))50
(assert-equal (list "ab" "cd" "") (rows-of t))51
(assert-equal (list 1 2) (cursor-of t))))52
(test "CR overprint"53
(assert-equal (list "Xbc" "" "") (rows-of (vt* 10 3 "abc\rX"))))54
(test "BS then overprint"55
(assert-equal (list "aX" "" "") (rows-of (vt* 10 3 "ab\x08;X"))))56
(test "autowrap wraps"57
(let ((t (vt* 10 3 "0123456789AB")))58
(assert-equal (list "0123456789" "AB" "") (rows-of t))59
(assert-equal (list 1 2) (cursor-of t))))60
(test "pending wrap: cursor stays on last col, CR cancels"61
(let ((t (vt* 10 3 "0123456789")))62
(assert-equal (list 0 9) (cursor-of t))63
(vt-feed! t "\rX")64
(assert-equal (list "X123456789" "" "") (rows-of t))))65
(test "DECAWM off: no wrap"66
(assert-equal (list "012345678B" "" "")67
(rows-of (vt* 10 3 (csi "?7l") "0123456789AB"))))68
(test "LF at bottom scrolls; evicted row to scrollback"69
(let ((t (vt* 5 2 "aa\r\nbb\r\ncc")))70
(assert-equal (list "bb" "cc") (rows-of t))71
(assert-equal 1 (vt-scrollback-count t))72
(assert-true (= (vt-cell-ch (vector-ref (vt-scrollback-row t 0) 0)) 97))))73
(test "tab to col 8"74
(assert-equal (list "a b" "") (rows-of (vt* 20 2 "a\tb")))))76
;; ==========================================================================77
;; DEC Special Graphics (tmux/ncurses line drawing)78
;; ==========================================================================79
(test-group "DEC Special Graphics"80
(test "G0 line drawing returns to ASCII"81
(assert-equal (list "┌──┐qq" "")82
(rows-of (vt* 20 2 ESC "(0lqqk" ESC "(Bqq"))))83
(test "G1 designation waits for SO; SI restores G0"84
(assert-equal (list "q─│q" "")85
(rows-of (vt* 20 2 ESC ")0q\x0e;qx\x0f;q"))))86
(test "byte feeds preserve designation and invocation across chunks"87
(let ((t (vt-make 20 2)))88
(for-each (lambda (b) (vt-feed-bytes! t (list b)))89
'(27 41 48 14 108 113 107 15 113))90
(assert-equal (list "┌─┐q" "") (rows-of t))))91
(test "string feeds preserve a split designation"92
(assert-equal (list "─q" "")93
(rows-of (vt* 20 2 ESC "(" "0" "q" ESC "(B" "q"))))94
(test "Unicode and ASCII outside the graphics range are unchanged"95
(assert-equal (list "AZ_─é─" "")96
(rows-of (vt* 20 2 ESC "(0AZ_─éq"))))97
(test "all DEC glyphs"98
(assert-equal (list "◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·" "")99
(rows-of (vt* 40 2 ESC "(0`abcdefghijklmnopqrstuvwxyz{|}~"))))100
(test "DECSC/DECRC restores designations and active set"101
(assert-equal (list "─q" "")102
(rows-of (vt* 20 2 ESC ")0\x0e;" ESC "7"103
ESC ")B\x0f;" ESC "8q\x0f;q"))))104
(test "alternate screen return restores graphics state"105
(assert-equal (list "─q" "")106
(rows-of (vt* 20 2 ESC "(0" (csi "?1049h")107
ESC "(B" (csi "?1049l") "q" ESC "(Bq"))))108
(test "reset clears both designations and invocation"109
(assert-equal (list "qq" "")110
(rows-of (vt* 20 2 ESC "(0" ESC ")0\x0e;"111
ESC "cq\x0e;q"))))112
(test "insert mode uses the same graphics translation"113
(assert-equal (list "─AB" "")114
(rows-of (vt* 20 2 "AB\r" (csi "4h") ESC "(0q"))))115
(test "escape and cancellation interrupt an incomplete designation"116
(assert-equal (list "q──" "")117
(rows-of (vt* 20 2 ESC "(\x18;q" ESC "("118
ESC "(0q" ESC "(\x1a;q"))))119
(test "graphics remain Unicode in scrollback and row runs"120
(let ((t (vt* 5 2 ESC "(0lqqk\r\nx x\r\nmqqj")))121
(assert-equal #x250c122
(vt-cell-ch (vector-ref (vt-scrollback-row t 0) 0)))123
(assert-equal "│ │" (vector-ref (car (vt-row-runs t 0)) 0)))))125
;; ==========================================================================126
;; cursor movement127
;; ==========================================================================128
(test-group "cursor movement"129
(test "CUP 3;4"130
(assert-equal (list "" "" " X" "" "") (rows-of (vt* 10 5 (csi "3;4H") "X"))))131
(test "CUU + CUB"132
(assert-equal (list "" " X" "" "" "")133
(rows-of (vt* 10 5 (csi "3;4H") (csi "A") (csi "2D") "X"))))134
(test "CUP clamps"135
(let ((t (vt* 10 5 "abc" (csi "10;20H") "Z")))136
(assert-equal (list "abc" "" "" "" " Z") (rows-of t))137
(assert-equal (list 4 9) (cursor-of t))))138
(test "CUD + CUF"139
(assert-equal (list 4 4)140
(cursor-of (vt* 10 5 (csi "2;2H") (csi "3B") (csi "2C") "X"))))141
(test "CHA column"142
(assert-equal (list "hi X" "" "" "" "") (rows-of (vt* 10 5 "hi" (csi "5G") "X"))))143
(test "VPA row keeps col"144
(assert-equal (list "hi" "" " X" "" "") (rows-of (vt* 10 5 "hi" (csi "3d") "X")))))146
;; ==========================================================================147
;; erase / insert / delete148
;; ==========================================================================149
(test-group "erase / insert / delete"150
(test "EL 0: erase to right"151
(assert-equal (list "abc" "" "") (rows-of (vt* 10 3 "abcdef" (csi "4G") (csi "K")))))152
(test "EL 1: erase to left (incl cursor)"153
(assert-equal (list " ef" "" "") (rows-of (vt* 10 3 "abcdef" (csi "4G") (csi "1K")))))154
(test "EL 2: whole line"155
(assert-equal (list "" "" "") (rows-of (vt* 10 3 "abcdef" (csi "2K")))))156
(test "ED 0: erase below"157
(assert-equal (list "aaaaaa" "bb" "")158
(rows-of (vt* 6 3 "aaaaaa\r\nbbbbbb\r\ncccccc" (csi "2;3H") (csi "J")))))159
(test "ED 1: erase above"160
(assert-equal (list "" " bbb" "cccccc")161
(rows-of (vt* 6 3 "aaaaaa\r\nbbbbbb\r\ncccccc" (csi "2;3H") (csi "1J")))))162
(test "ED 2: erase all + cursor left on last col"163
(let ((t (vt* 6 3 "aaaaaa\r\nbbbbbb" (csi "2J"))))164
(assert-equal (list "" "" "") (rows-of t))165
(assert-equal (list 1 5) (cursor-of t))))166
(test "ICH inserts blanks (then overtyped)"167
(assert-equal (list "abXYcdef" "" "")168
(rows-of (vt* 10 3 "abcdef" (csi "3G") (csi "2@") "XY"))))169
(test "DCH deletes chars"170
(assert-equal (list "abef" "" "") (rows-of (vt* 10 3 "abcdef" (csi "3G") (csi "2P")))))171
(test "ECH erases chars in place"172
(assert-equal (list "ab ef" "" "") (rows-of (vt* 10 3 "abcdef" (csi "3G") (csi "2X")))))173
(test "IL inserts a line"174
(assert-equal (list "a" "" "b" "c")175
(rows-of (vt* 5 4 "a\r\nb\r\nc\r\nd" (csi "2;1H") (csi "L")))))176
(test "DL deletes a line"177
(assert-equal (list "a" "c" "d" "")178
(rows-of (vt* 5 4 "a\r\nb\r\nc\r\nd" (csi "2;1H") (csi "M"))))))180
;; ==========================================================================181
;; scroll region182
;; ==========================================================================183
(test-group "scroll region"184
(test "DECSTBM: region scrolls, top intact"185
(assert-equal (list "top" "l2" "l3" "l4" "")186
(rows-of (vt* 5 5 "top" (csi "2;4r") (csi "2;1H") "l1\r\nl2\r\nl3\r\nl4"))))187
(test "SU in region"188
(assert-equal (list "a" "d" "" "" "e")189
(rows-of (vt* 5 5 "a\r\nb\r\nc\r\nd\r\ne" (csi "2;4r") (csi "2S")))))190
(test "SD in region"191
(assert-equal (list "a" "" "b" "c" "e")192
(rows-of (vt* 5 5 "a\r\nb\r\nc\r\nd\r\ne" (csi "2;4r") (csi "1T")))))193
(test "RI at region top"194
(assert-equal (list "a" "" "b" "c" "")195
(rows-of (vt* 5 5 "a\r\nb\r\nc" (csi "2;4r") (csi "2;1H") ESC "M"))))196
(test "non-top region scroll: no scrollback"197
(assert-equal 0 (vt-scrollback-count198
(vt* 5 5 "x\r\ny" (csi "2;4r") (csi "2;1H") "1\r\n2\r\n3\r\n4"))))199
(test "DECOM: home is region top"200
(assert-equal (list "" "X" "" "" "")201
(rows-of (vt* 10 5 (csi "2;4r") (csi "?6h") (csi "1;1H") "X")))))203
;; ==========================================================================204
;; SGR205
;; ==========================================================================206
(test-group "SGR"207
(test "SGR bold red + reset"208
(let ((t (vt* 10 2 (csi "1;31m") "R" (csi "0m") "p")))209
(assert-equal (list "R" vt-attr-bold 1 -1) (cell-at t 0 0))210
(assert-equal (list "p" 0 -1 -1) (cell-at t 0 1))))211
(test "SGR italic+underline+inverse"212
(assert-equal (list "x" (+ vt-attr-italic vt-attr-underline vt-attr-inverse) -1 -1)213
(cell-at (vt* 10 2 (csi "3;4;7m") "x") 0 0)))214
(test "SGR 256-color fg/bg"215
(assert-equal (list "c" 0 196 22)216
(cell-at (vt* 10 2 (csi "38;5;196m") (csi "48;5;22m") "c") 0 0)))217
(test "SGR truecolor fg"218
(assert-equal (list "t" 0 (+ #x1000000 (* 255 65536) (* 128 256) 0) -1)219
(cell-at (vt* 10 2 (csi "38;2;255;128;0m") "t") 0 0)))220
(test "SGR colon syntax"221
(assert-equal (list "Q" 0 99 -1) (cell-at (vt* 10 2 ESC "[38:5:99mQ") 0 0)))222
(test "SGR colon colorspace form + following param"223
(assert-equal (list "W" vt-attr-underline (+ #x1000000 (* 255 65536) (* 128 256) 0) -1)224
(cell-at (vt* 10 2 ESC "[38:2::255:128:0;4mW") 0 0)))225
(test "SGR colon truecolor (no colorspace)"226
(assert-equal (list "V" 0 (+ #x1000000 (* 10 65536) (* 20 256) 30) -1)227
(cell-at (vt* 10 2 ESC "[38:2:10:20:30mV") 0 0)))228
(test "SGR bright fg + 39 default"229
(let ((t (vt* 10 2 (csi "91m") "b" (csi "39m") "d")))230
(assert-equal (list "b" 0 9 -1) (cell-at t 0 0))231
(assert-equal (list "d" 0 -1 -1) (cell-at t 0 1))))232
(test "SGR 22 clears bold, keeps color"233
(assert-equal (list "b" 0 1 -1)234
(cell-at (vt* 10 2 (csi "1;31m") "a" (csi "22m") "b") 0 1)))235
(test "BCE: ED fills with cur bg"236
(assert-equal 19 (vt-cell-bg (vector-ref (vt-row-cells (vt* 4 2 (csi "48;5;19m") (csi "2J")) 1) 3))))237
(test "palette 16 = cube 0,0,0" (assert-equal 0 (vt-color-256->rgb 16)))238
(test "palette 196 = red" (assert-equal #xff0000 (vt-color-256->rgb 196)))239
(test "palette 231 = white" (assert-equal #xffffff (vt-color-256->rgb 231)))240
(test "palette 244 gray" (assert-equal #x808080 (vt-color-256->rgb 244))))242
;; ==========================================================================243
;; alt screen244
;; ==========================================================================245
(test-group "alt screen"246
(test "1049: alt starts cleared / restores main + cursor"247
(let ((t (vt* 10 3 "main" (csi "?1049h") (csi "1;1H") "ALT")))248
(assert-equal (list "ALT" "" "") (rows-of t))249
(assert-true (vt-alt? t))250
(vt-feed! t (csi "?1049l"))251
(assert-equal (list "main" "" "") (rows-of t))252
(assert-equal (list 0 4) (cursor-of t))253
(assert-true (not (vt-alt? t)))))254
(test "alt: no scrollback"255
(assert-equal 0 (vt-scrollback-count (vt* 5 2 (csi "?1049h") "a\r\nb\r\nc\r\nd")))))257
;; ==========================================================================258
;; DECSC/DECRC, DECALN, RIS259
;; ==========================================================================260
(test-group "DECSC/DECRC, DECALN, RIS"261
(test "DECSC/DECRC restores pos + SGR"262
(let ((t (vt* 10 3 (csi "31m") (csi "2;3H") ESC "7" (csi "0m") (csi "1;1H") ESC "8" "X")))263
(assert-equal (list "" " X" "") (rows-of t))264
(assert-equal (list "X" 0 1 -1) (cell-at t 1 2))))265
(test "DECALN fills E"266
(assert-equal (list "EEE" "EEE") (rows-of (vt* 3 2 ESC "#8"))))267
(test "RIS clears + resets SGR"268
(let ((t (vt* 5 2 "hi" (csi "31m") ESC "c" "x")))269
(assert-equal (list "x" "") (rows-of t))270
(assert-equal (list "x" 0 -1 -1) (cell-at t 0 0)))))272
;; ==========================================================================273
;; OSC title, DECSCUSR, bracketed paste, modes, events274
;; ==========================================================================275
(test-group "OSC / modes / events"276
(test "OSC 0 BEL: title"277
(let ((t (vt* 10 2 ESC "]0;my title\x07;" "x")))278
(assert-equal "my title" (vt-title t))279
(assert-equal (list "x" "") (rows-of t))))280
(test "OSC 2 ST: title"281
(let ((t (vt* 10 2 ESC "]2;st title" ESC "\\" "y")))282
(assert-equal "st title" (vt-title t))283
(assert-equal (list "y" "") (rows-of t))))284
(test "OSC 52 (clipboard) not in title, queued as event"285
(let ((t (vt* 10 2 ESC "]52;c;aGVsbG8=\x07;" "z")))286
(assert-equal "" (vt-title t))287
(assert-equal (list "z" "") (rows-of t))288
(let ((evs (vt-take-events! t)))289
(assert-true (memv 'clipboard (map (lambda (e) (dict-ref e type: #f)) evs))))))290
(test "DECSCUSR style" (assert-equal 4 (vt-cursor-style (vt* 10 2 (csi "4 q")))))291
(test "bracketed paste on" (assert-true (vt-bracketed-paste? (vt* 10 2 (csi "?2004h")))))292
(test "bracketed paste off"293
(assert-true (not (vt-bracketed-paste? (vt* 10 2 (csi "?2004h") (csi "?2004l"))))))294
(test "cursor hidden" (assert-true (not (vt-cursor-visible? (vt* 10 2 (csi "?25l"))))))295
(test "app cursor mode" (assert-true (vt-app-cursor? (vt* 10 2 (csi "?1h")))))296
(test "IRM: chars shift right"297
(assert-equal (list "aXYbc" "") (rows-of (vt* 10 2 "abc" (csi "2G") (csi "4h") "XY"))))298
(test "mouse-mode flags parsed"299
(assert-equal (+ vt-mouse-1002 vt-mouse-1006)300
(vt-mouse-flags (vt* 10 2 (csi "?1002h") (csi "?1006h")))))301
(test "mouse-mode flags cleared"302
(assert-equal vt-mouse-1006303
(vt-mouse-flags (vt* 10 2 (csi "?1002h") (csi "?1006h") (csi "?1002l"))))))305
;; ==========================================================================306
;; replies (DSR / DA)307
;; ==========================================================================308
(test-group "replies"309
(test "DSR 6: cursor report + drain"310
(let ((t (vt* 10 5 (csi "3;4H") (csi "6n"))))311
(assert-equal (string-append ESC "[3;4R") (vt-take-output! t))312
(assert-equal "" (vt-take-output! t))))313
(test "DSR 5: status ok"314
(assert-equal (string-append ESC "[0n") (vt-take-output! (vt* 10 5 (csi "5n")))))315
(test "DA reply"316
(assert-equal (string-append ESC "[?6c") (vt-take-output! (vt* 10 5 (csi "c"))))))318
;; ==========================================================================319
;; the trust boundary320
;; ==========================================================================321
(test-group "trust boundary"322
(test "huge params clamp"323
(let ((t (vt* 10 2 ESC "[999999999999H" "ok")))324
(assert-equal (list 1 2) (cursor-of t))325
(assert-equal (list "" "ok") (rows-of t))))326
(test "truncated SGR ignored"327
(assert-equal (list "x" 0 -1 -1) (cell-at (vt* 10 2 ESC "[38;2m" "x") 0 0)))328
(test "DCS swallowed"329
(assert-equal (list "ok" "") (rows-of (vt* 10 2 ESC "P malicious dcs payload " ESC "\\" "ok"))))330
(test "CHT clamps"331
(assert-equal (list 0 9) (cursor-of (vt* 10 2 ESC "[999999999I" "x"))))332
(test "unknown modes/charsets swallowed"333
(assert-equal (list "ok" "") (rows-of (vt* 10 2 ESC "[?9999h" ESC "[<5m" ESC "(X" "ok"))))334
(test "escape split across chunks"335
(let ((t (vt-make 10 2)))336
(vt-feed! t ESC) (vt-feed! t "[3") (vt-feed! t "1mX")337
(assert-equal (list "X" 0 1 -1) (cell-at t 0 0)))))339
;; ==========================================================================340
;; incremental UTF-8 (byte feed)341
;; ==========================================================================342
(test-group "incremental UTF-8"343
(test "utf-8 across chunks"344
(let ((t (vt-make 10 2)))345
(vt-feed-bytes! t (list 97 195))346
(vt-feed-bytes! t (list 169 32 226 134))347
(vt-feed-bytes! t (list 146))348
(assert-equal "aé → " (vt-row-text t 0))))349
(test "invalid utf-8 -> U+FFFD, stream recovers"350
(let ((t (vt-make 10 2)))351
(vt-feed-bytes! t (list 195 195 169))352
(assert-true (and (= (vt-cell-ch (vector-ref (vt-row-cells t 0) 0)) 65533)353
(= (vt-cell-ch (vector-ref (vt-row-cells t 0) 1)) 233)))))354
(test "surrogate bytes -> U+FFFD"355
(let ((t (vt-make 10 2)))356
(vt-feed-bytes! t (list 237 160 128))357
(assert-equal 65533 (vt-cell-ch (vector-ref (vt-row-cells t 0) 0)))))358
(test "past-U+10FFFF -> U+FFFD"359
(let ((t (vt-make 10 2)))360
(vt-feed-bytes! t (list 244 144 128 128))361
(assert-equal 65533 (vt-cell-ch (vector-ref (vt-row-cells t 0) 0)))))362
(test "overlong encoding -> U+FFFD"363
(let ((t (vt-make 10 2)))364
(vt-feed-bytes! t (list 224 128 168))365
(assert-equal 65533 (vt-cell-ch (vector-ref (vt-row-cells t 0) 0)))))366
(test "DL never scrollbacks"367
(let ((t (vt* 5 3 "a\r\nb\r\nc" (csi "1;1H") (csi "M"))))368
(assert-equal 0 (vt-scrollback-count t))369
(assert-equal (list "b" "c" "") (rows-of t)))))371
;; ==========================================================================372
;; damage tracking373
;; ==========================================================================374
(test-group "damage"375
(test "damage: one row / drained"376
(let ((t (vt-make 10 3)))377
(vt-take-damage! t)378
(vt-feed! t "x")379
(let ((d (vt-take-damage! t)))380
(assert-equal (list 0) (dict-ref d rows: '()))381
(assert-true (not (dict-ref d all?: #f))))382
(assert-equal '() (dict-ref (vt-take-damage! t) rows: '()))))383
(test "ED marks all its rows"384
(let ((t (vt-make 10 3)))385
(vt-take-damage! t)386
(vt-feed! t (csi "2J"))387
(assert-equal (list 0 1 2) (dict-ref (vt-take-damage! t) rows: '()))))388
(test "scroll damages the region"389
(let ((t (vt-make 5 2)))390
(vt-take-damage! t)391
(vt-feed! t "a\r\nb\r\nc")392
(let ((d (vt-take-damage! t)))393
(assert-true (or (dict-ref d all?: #f)394
(equal? (dict-ref d rows: '()) (list 0 1))))))))396
;; ==========================================================================397
;; resize398
;; ==========================================================================399
(test-group "resize"400
(test "narrower truncates / wider pads / cols updated"401
(let ((t (vt* 10 4 "aa\r\nbb")))402
(vt-resize! t 5 4)403
(assert-equal (list "aa" "bb" "" "") (rows-of t))404
(vt-resize! t 20 4)405
(assert-equal (list "aa" "bb" "" "") (rows-of t))406
(assert-equal 20 (vt-cols t))))407
(test "shrink drops blank bottom rows (no scrollback)"408
(let ((t (vt* 10 5 "aa\r\nbb")))409
(vt-resize! t 10 3)410
(assert-equal (list "aa" "bb" "") (rows-of t))411
(assert-equal 0 (vt-scrollback-count t))))412
(test "shrink onto content keeps cursor rows, evicts to scrollback"413
(let ((t (vt* 10 4 "a\r\nb\r\nc\r\nd")))414
(vt-resize! t 10 2)415
(assert-equal (list "c" "d") (rows-of t))416
(assert-equal 2 (vt-scrollback-count t))417
(assert-equal (list 1 1) (cursor-of t))))418
(test "grow pulls back out of scrollback"419
(let ((t (vt* 10 2 "a\r\nb\r\nc")))420
(assert-equal 1 (vt-scrollback-count t))421
(vt-resize! t 10 4)422
(assert-equal (list "a" "b" "c" "") (rows-of t))423
(assert-equal 0 (vt-scrollback-count t))))424
(test "post-resize feed is sane"425
(let ((t (vt* 10 4 (csi "2;3r") "x")))426
(vt-resize! t 8 3)427
(vt-feed! t (string-append (csi "3;1H") "\n\n"))428
(assert-true (>= (vt-rows t) 3)))))430
;; ==========================================================================431
;; the ground-state bulk-run fast path432
;; ==========================================================================433
(test-group "bulk-run fast path"434
(test "run wraps across the margin"435
(let ((t (vt* 5 3 "abcdefgh")))436
(assert-equal (list "abcde" "fgh" "") (rows-of t))437
(assert-equal (list 1 3) (cursor-of t))))438
(test "run deferred wrap parks / fires next char"439
(let ((t (vt* 5 3 "abcde")))440
(assert-equal (list 0 4) (cursor-of t))441
(vt-feed! t "f")442
(assert-equal (list "abcde" "f" "") (rows-of t))))443
(test "run no-autowrap overwrites last col"444
(let ((t (vt* 5 3 (csi "?7l") "abcdefgh")))445
(assert-equal (list "abcdh" "" "") (rows-of t))446
(assert-equal (list 0 4) (cursor-of t))))447
(test "esc splits runs, text intact"448
(assert-equal (list "abcdef" "" "")449
(rows-of (vt* 10 3 (string-append "ab" (csi "31m") "cd" (csi "0m") "ef")))))450
(test "insert mode shifts, not overwrites"451
(assert-equal (list "XYabc" "" "")452
(rows-of (vt* 10 3 "abc" (csi "1;1H") (csi "4h") "XY"))))453
(test "mixed unicode intact"454
(assert-equal (list "aλbμc" "" "") (rows-of (vt* 10 3 "aλbμc")))))456
;; ==========================================================================457
;; row-runs (the render seam) — new native primitive458
;; ==========================================================================459
(test-group "row-runs"460
(test "plain run merges to one, right-trimmed"461
(let* ((t (vt* 10 2 "hello"))462
(runs (vt-row-runs t 0)))463
(assert-equal 1 (length runs))464
(assert-equal "hello" (vector-ref (car runs) 0))465
(assert-equal 0 (vector-ref (car runs) 1))))466
(test "style change splits runs"467
(let* ((t (vt* 10 2 "ab" (csi "31m") "cd"))468
(runs (vt-row-runs t 0)))469
(assert-equal 2 (length runs))470
(assert-equal "ab" (vector-ref (car runs) 0))471
(assert-equal "cd" (vector-ref (cadr runs) 0))472
(assert-equal 1 (vector-ref (cadr runs) 2))))473
(test "cursor-col forces a break with cursor? flag"474
(let* ((t (vt* 10 2 "hello"))475
(runs (vt-row-runs t 0 1)))476
;; "h" | "e"(cursor) | "llo"477
(assert-equal 3 (length runs))478
(assert-true (vector-ref (cadr runs) 4))479
(assert-equal "e" (vector-ref (cadr runs) 0))))480
(test "blank row -> no runs"481
(assert-equal '() (vt-row-runs (vt-make 10 2) 1)))483
;; ---- t-d4c7: a history row keeps the width it was PUSHED at -------------484
;; Ring rows are allocated at the cols in effect when they scrolled off and485
;; are never re-widthed (xterm no-rewrap). Reading one at t->cols after a486
;; WIDENING resize runs off the end of the allocation: a heap over-read whose487
;; garbage got rendered into the terminal (DoS via integer->char, plus488
;; disclosure of adjacent heap). These call the REAL readers — the fuzz489
;; harness cannot, since VT_FUZZ compiles the Sigil glue out.490
(test "scrollback row keeps its push width (no over-read on widen)"491
(let ((t (vt-make 40 3)))492
(vt-feed! t "aaaa\r\nbbbb\r\ncccc\r\ndddd\r\neeee\r\n")493
(assert-true (> (vt-scrollback-count t) 0))494
(vt-resize! t 132 3) ; widen; history stays 40 wide495
(assert-equal 132 (vt-cols t))496
;; the row is returned at ITS width, not the grid's497
(assert-equal 40 (vector-length (vt-scrollback-row t 0)))498
;; and every cell is a real codepoint, not heap garbage499
(let* ((row (vt-scrollback-row t 0))500
(n (vector-length row)))501
(let loop ((i 0))502
(when (< i n)503
(let ((cp (vt-cell-ch (vector-ref row i))))504
(assert-true (and (>= cp 0) (<= cp 1114111))))505
(loop (+ i 1)))))))507
(test "scrollback runs after widen stay in-bounds"508
(let ((t (vt-make 40 3)))509
(vt-feed! t "hello\r\nworld\r\nagain\r\nmore1\r\nmore2\r\n")510
(vt-resize! t 132 3)511
(let ((runs (vt-scrollback-runs t 0)))512
(assert-true (pair? runs))513
;; TOTAL rendered width must not exceed the row's real width. Asserting514
;; per-RUN length instead would silently pass on the bug: over-read515
;; garbage has erratic attrs, so it splits into many SHORT runs that are516
;; each under the limit while the row as a whole runs far over.517
(let loop ((rs runs) (total 0))518
(if (null? rs)519
(assert-true (<= total 40))520
(loop (cdr rs) (+ total (string-length (vector-ref (car rs) 0)))))))))522
;; The resize PULL had the same root cause with a different guess (ocols, not523
;; t->cols) — only wrong after TWO resizes, when ocols is neither the push524
;; width nor the new width.525
(test "resize pull uses the row's push width, not ocols"526
(let ((t (vt-make 40 3)))527
(vt-feed! t "aaaa\r\nbbbb\r\ncccc\r\ndddd\r\neeee\r\n")528
(vt-resize! t 80 3) ; ocols becomes 80...529
(vt-resize! t 132 8) ; ...but history rows are 40 wide530
(assert-equal 132 (vt-cols t))531
;; pulled-back rows must be real content, not garbage532
(let loop ((r 0))533
(when (< r 8)534
(let* ((row (vt-row-cells t r))535
(n (vector-length row)))536
(let loop2 ((i 0))537
(when (< i n)538
(let ((cp (vt-cell-ch (vector-ref row i))))539
(assert-true (and (>= cp 0) (<= cp 1114111))))540
(loop2 (+ i 1)))))541
(loop (+ r 1)))))))