Commitba04e66cRecorded26 Feb 2026Repositorysigil-tui

Add chat demo and optimize TUI rendering performance

Message

Demo app exercises all TUI components with a simulated chat client.

Performance optimizations: - Mutable cells: grid-set! mutates existing cell vectors in place instead of allocating new ones, eliminating thousands of vector allocations per frame - Skip idle renders: only render after input events, not on timeout - Input draining: process all buffered input before rendering once - Inline cell access in grid-diff to reduce function call overhead

Changed
 examples/chat-demo.sgl       | 275 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/sigil/tui.sgl            |  98 ++++++++++++++++++++++++++++++++++++----------------------
 src/sigil/tui/components.sgl |  33 +++++++++-----------
 src/sigil/tui/grid.sgl       |  69 ++++++++++++++++++++++++++++-------------
 test/test-grid.sgl           |  19 +++++-------
 5 files changed, 407 insertions(+), 87 deletions(-)
Diff
examples/chat-demo.sgladded
@@ -0,0 +1,275 @@
+1
;;; Chat Demo — exercises all sigil-tui components
+2
;;;
+3
;;; Run with:
+4
;;; ./build/dev/bin/sigil run packages/sigil-tui/examples/chat-demo.sgl
+5
+6
(import (sigil core)
+7
(sigil struct)
+8
(sigil math)
+9
(sigil time)
+10
(sigil tui))
+11
+12
;; ============================================================
+13
;; Data model
+14
;; ============================================================
+15
+16
(define-struct contact
+17
(name default: "")
+18
(online? default: #f)
+19
(group? default: #f))
+20
+21
(define-struct message
+22
(sender default: "")
+23
(text default: "")
+24
(timestamp default: 0))
+25
+26
;; Application state — all mutable for easy updates
+27
(define-struct app-state
+28
(contacts default: '() mutable: #t)
+29
(selected default: 0 mutable: #t)
+30
(conversations default: '() mutable: #t) ;; alist: name -> list of messages
+31
(input default: #f mutable: #t)
+32
(mode default: 'roster mutable: #t) ;; 'roster or 'chat
+33
(scroll-offset default: 0 mutable: #t)
+34
(last-tick default: 0 mutable: #t)
+35
(sim-counter default: 0 mutable: #t))
+36
+37
;; ============================================================
+38
;; Helpers
+39
;; ============================================================
+40
+41
(define (format-time ts)
+42
(let* ((total (exact (floor ts)))
+43
(hours (modulo (quotient total 3600) 24))
+44
(mins (modulo (quotient total 60) 60))
+45
(h-str (if (< hours 10)
+46
(string-append "0" (number->string hours))
+47
(number->string hours)))
+48
(m-str (if (< mins 10)
+49
(string-append "0" (number->string mins))
+50
(number->string mins))))
+51
(string-append h-str ":" m-str)))
+52
+53
(define (get-conversation state name)
+54
(let ((entry (assoc name (app-state-conversations state))))
+55
(if entry (cdr entry) '())))
+56
+57
(define (add-message! state contact-name sender text)
+58
(let* ((msg (message sender: sender
+59
text: text
+60
timestamp: (current-second)))
+61
(convs (app-state-conversations state))
+62
(entry (assoc contact-name convs))
+63
(msgs (if entry (cdr entry) '()))
+64
(new-msgs (append msgs (list msg))))
+65
(if entry
+66
(set-cdr! entry new-msgs)
+67
(set-app-state-conversations! state
+68
(cons (cons contact-name new-msgs) convs)))))
+69
+70
(define (selected-contact state)
+71
(let ((contacts (app-state-contacts state))
+72
(idx (app-state-selected state)))
+73
(if (< idx (length contacts))
+74
(list-ref contacts idx)
+75
#f)))
+76
+77
;; ============================================================
+78
;; Simulated incoming messages
+79
;; ============================================================
+80
+81
(define *sim-messages*
+82
'(("alice" "Hey, how's it going?")
+83
("alice" "Working on anything fun?")
+84
("bob" "Did you see the game last night?")
+85
("alice" "I just finished the new TUI library!")
+86
("#general" "Welcome to #general!")
+87
("bob" "Let me know when you're free")
+88
("#general" "Anyone working on something cool?")
+89
("alice" "The grid diffing is so fast")
+90
("bob" "Pizza for lunch?")
+91
("#general" "Reminder: standup at 10am")))
+92
+93
(define (maybe-simulate-message! state)
+94
(let ((now (current-second))
+95
(last (app-state-last-tick state)))
+96
(when (> (- now last) 3.0)
+97
(set-app-state-last-tick! state now)
+98
(let* ((idx (modulo (app-state-sim-counter state)
+99
(length *sim-messages*)))
+100
(entry (list-ref *sim-messages* idx))
+101
(contact-name (car entry))
+102
(text (cadr entry))
+103
(sender (if (and (> (string-length contact-name) 0)
+104
(char=? (string-ref contact-name 0) #\#))
+105
"someone"
+106
contact-name)))
+107
(add-message! state contact-name sender text)
+108
(set-app-state-sim-counter! state
+109
(+ 1 (app-state-sim-counter state)))))))
+110
+111
;; ============================================================
+112
;; UI rendering
+113
;; ============================================================
+114
+115
(define (render-roster state)
+116
(let* ((contacts (app-state-contacts state))
+117
(items (map (lambda (c)
+118
(let* ((dot (if (contact-online? c) "● " "○ "))
+119
(name (contact-name c))
+120
(label (string-append dot name))
+121
(fg (if (contact-online? c)
+122
color-green
+123
color-bright-black))
+124
(bg color-default))
+125
(list label fg bg attr-none)))
+126
contacts)))
+127
(selectable-list items (app-state-selected state))))
+128
+129
(define (render-chat-messages state contact-name)
+130
(let* ((msgs (get-conversation state contact-name))
+131
(lines (map (lambda (m)
+132
(let* ((time-str (format-time (message-timestamp m)))
+133
(sender (message-sender m))
+134
(text (message-text m))
+135
(line (string-append
+136
"[" time-str "] "
+137
sender ": " text)))
+138
(if (string=? sender "you")
+139
(list line color-cyan color-default attr-none)
+140
(list line color-default color-default attr-none))))
+141
msgs)))
+142
(if (null? lines)
+143
(text-block (list (list "No messages yet."
+144
color-bright-black color-default attr-italic)))
+145
(text-block lines))))
+146
+147
(define (render-app state)
+148
(let* ((contact (selected-contact state))
+149
(contact-name (if contact (contact-name contact) ""))
+150
(chat-title (string-append "Chat: " contact-name))
+151
(input-st (app-state-input state))
+152
(prompt (string-append contact-name "> "))
+153
(status-contact (if contact
+154
(string-append contact-name
+155
(if (contact-online? contact)
+156
" (online)"
+157
" (offline)"))
+158
""))
+159
(mode-str (if (eq? (app-state-mode state) 'roster)
+160
"[roster]"
+161
"[chat]"))
+162
(status-right (string-append mode-str " Ctrl-Q quit")))
+163
(vertical-split 0.92
+164
(horizontal-split 0.25
+165
(panel "Roster" (render-roster state))
+166
(vertical-split 0.88
+167
(panel chat-title
+168
(render-chat-messages state contact-name))
+169
(panel ""
+170
(text-input-widget prompt
+171
(input-value input-st)
+172
(input-cursor input-st)))))
+173
(status-bar "chat-demo" status-contact status-right))))
+174
+175
;; ============================================================
+176
;; Event handling
+177
;; ============================================================
+178
+179
(define (handle-event state event)
+180
(maybe-simulate-message! state)
+181
+182
(cond
+183
;; Quit
+184
((and (key-event? event)
+185
(equal? event '(key #\q ctrl)))
+186
'quit)
+187
+188
;; Tab to switch focus mode
+189
((and (key-event? event)
+190
(equal? event '(key tab)))
+191
(set-app-state-mode! state
+192
(if (eq? (app-state-mode state) 'roster) 'chat 'roster))
+193
state)
+194
+195
;; Roster mode keys
+196
((eq? (app-state-mode state) 'roster)
+197
(handle-roster-event state event))
+198
+199
;; Chat mode keys
+200
((eq? (app-state-mode state) 'chat)
+201
(handle-chat-event state event))
+202
+203
(else state)))
+204
+205
(define (handle-roster-event state event)
+206
(cond
+207
((and (key-event? event)
+208
(equal? event '(key up)))
+209
(let ((sel (app-state-selected state)))
+210
(when (> sel 0)
+211
(set-app-state-selected! state (- sel 1))))
+212
state)
+213
+214
((and (key-event? event)
+215
(equal? event '(key down)))
+216
(let ((sel (app-state-selected state))
+217
(max-idx (- (length (app-state-contacts state)) 1)))
+218
(when (< sel max-idx)
+219
(set-app-state-selected! state (+ sel 1))))
+220
state)
+221
+222
;; Enter or right arrow switches to chat
+223
((and (key-event? event)
+224
(or (equal? event '(key enter))
+225
(equal? event '(key right))))
+226
(set-app-state-mode! state 'chat)
+227
state)
+228
+229
(else state)))
+230
+231
(define (handle-chat-event state event)
+232
(cond
+233
;; Left arrow goes back to roster
+234
((and (key-event? event)
+235
(equal? event '(key left)))
+236
(set-app-state-mode! state 'roster)
+237
state)
+238
+239
;; Pass key events to input
+240
((key-event? event)
+241
(let ((result (input-handle-key (app-state-input state) event)))
+242
(when (eq? result 'submit)
+243
(let* ((contact (selected-contact state))
+244
(text (input-value (app-state-input state))))
+245
(when (and contact (> (string-length text) 0))
+246
(add-message! state (contact-name contact) "you" text)
+247
(input-clear! (app-state-input state))))))
+248
state)
+249
+250
(else state)))
+251
+252
;; ============================================================
+253
;; Main
+254
;; ============================================================
+255
+256
(define (make-initial-state)
+257
(let ((state (app-state
+258
contacts: (list
+259
(contact name: "alice" online?: #t)
+260
(contact name: "bob" online?: #f)
+261
(contact name: "#general" online?: #t group?: #t)
+262
(contact name: "#random" online?: #f group?: #t))
+263
input: (make-input-state)
+264
last-tick: (current-second))))
+265
;; Seed some initial messages
+266
(add-message! state "alice" "alice" "Hey, welcome to the chat!")
+267
(add-message! state "alice" "you" "Thanks! Testing the TUI.")
+268
(add-message! state "#general" "admin" "Welcome to #general")
+269
state))
+270
+271
(tui-run
+272
render-app
+273
initial-state: (make-initial-state)
+274
on-event: handle-event
+275
fps: 30)
src/sigil/tui.sglmodified
@@ -91,7 +91,35 @@
91
'quit
92
(process-events new-state (cdr events) on-event)))))
93
94
;; Single frame: render, diff, write, wait for input, recurse
+94
;; Drain all pending input from stdin, processing events as they arrive.
+95
;; Returns the final state after all available input is consumed.
+96
(define (drain-input state parser on-event)
+97
(let ((ready (fd-select (list 0) '() 0)))
+98
(if (and ready (pair? (car ready)))
+99
(let ((raw (terminal-read-raw 256)))
+100
(if raw
+101
(let ((new-state (process-events
+102
state
+103
(parse-input parser raw)
+104
on-event)))
+105
(if (eq? new-state 'quit)
+106
'quit
+107
(drain-input new-state parser on-event)))
+108
state))
+109
state)))
+110
+111
;; Render state into curr grid, diff against prev, write changes.
+112
(define (do-render render state prev curr cols rows)
+113
(grid-clear! curr)
+114
(let* ((tree (render state))
+115
(commands (layout-tree tree 0 0 cols rows)))
+116
(render-commands curr commands))
+117
(let ((diff-str (grid-diff prev curr)))
+118
(when (> (string-length diff-str) 0)
+119
(terminal-write-raw diff-str))))
+120
+121
;; Main loop: wait for input, process it, render, repeat.
+122
;; Only renders after input or resize -- skips render on timeout.
123
(define (run-frame render on-event state parser prev curr
124
cols rows frame-ms)
125
;; Handle resize
@@ -103,44 +131,37 @@
131
(prev (if resize-info (make-grid cols rows) prev))
132
(curr (if resize-info (make-grid cols rows) curr)))
133
(when resize-info
106
(terminal-write-raw "\x1b;[2J"))
+134
(terminal-write-raw "\x1b;[2J")
+135
(do-render render state prev curr cols rows))
136
137
;; Handle interrupt
138
(if (signal-pending? 'int)
139
state
111
(begin
112
;; Render current frame
113
(grid-clear! curr)
114
(let* ((tree (render state))
115
(commands (layout-tree tree 0 0 cols rows)))
116
(render-commands curr commands))
117
118
;; Diff and write
119
(let ((diff-str (grid-diff prev curr)))
120
(when (> (string-length diff-str) 0)
121
(terminal-write-raw diff-str)))
122
123
;; Swap buffers
124
(let* ((new-prev (grid-copy curr))
125
;; Wait for input with frame timeout
126
(ready (fd-select (list 0) '() frame-ms)))
127
(if (and ready (pair? (car ready)))
128
;; Input available
129
(let ((raw (terminal-read-raw 256)))
130
(if raw
131
(let ((new-state (process-events
132
state
133
(parse-input parser raw)
134
on-event)))
135
(if (eq? new-state 'quit)
136
new-state
+140
;; Wait for input or frame timeout
+141
(let ((ready (fd-select (list 0) '() frame-ms)))
+142
(if (and ready (pair? (car ready)))
+143
;; Input available
+144
(let ((raw (terminal-read-raw 256)))
+145
(if (not raw)
+146
(run-frame render on-event state parser
+147
prev curr cols rows frame-ms)
+148
(let* ((new-state (process-events
+149
state
+150
(parse-input parser raw)
+151
on-event))
+152
(new-state (if (eq? new-state 'quit)
+153
'quit
+154
(drain-input new-state
+155
parser on-event))))
+156
(if (eq? new-state 'quit)
+157
'quit
+158
(begin
+159
(do-render render new-state prev curr cols rows)
160
(run-frame render on-event new-state parser
138
new-prev curr cols rows frame-ms)))
139
(run-frame render on-event state parser
140
new-prev curr cols rows frame-ms)))
141
;; Timeout - redraw next frame
142
(run-frame render on-event state parser
143
new-prev curr cols rows frame-ms)))))))
+161
curr prev cols rows frame-ms))))))
+162
;; Timeout -- no input, skip render
+163
(run-frame render on-event state parser
+164
prev curr cols rows frame-ms))))))
165
166
;;; Run a TUI application.
167
;;;
@@ -161,10 +182,13 @@
182
(let* ((size (terminal-size))
183
(cols (car size))
184
(rows (cdr size))
164
(frame-ms (exact (floor (/ 1000 fps)))))
+185
(frame-ms (exact (floor (/ 1000 fps))))
+186
(prev (make-grid cols rows))
+187
(curr (make-grid cols rows)))
+188
;; Initial render
+189
(do-render render initial-state prev curr cols rows)
190
(run-frame render on-event
191
initial-state
192
(make-parser-state)
168
(make-grid cols rows)
169
(make-grid cols rows)
+193
curr prev
194
cols rows frame-ms))))))))
src/sigil/tui/components.sglmodified
@@ -34,13 +34,13 @@
34
(define (render-panel grid x y w h title)
35
(when (and (> w 0) (> h 0))
36
;; Top border
37
(grid-set! grid x y (make-cell box-tl color-default color-default attr-none))
+37
(grid-set! grid x y box-tl color-default color-default attr-none)
38
(let loop ((c (+ x 1)))
39
(when (< c (+ x w -1))
40
(grid-set! grid c y (make-cell box-h color-default color-default attr-none))
+40
(grid-set! grid c y box-h color-default color-default attr-none)
41
(loop (+ c 1))))
42
(when (> w 1)
43
(grid-set! grid (+ x w -1) y (make-cell box-tr color-default color-default attr-none)))
+43
(grid-set! grid (+ x w -1) y box-tr color-default color-default attr-none))
44
45
;; Title (centered in top border)
46
(when (and title (> (string-length title) 0) (> w 4))
@@ -51,35 +51,33 @@
51
title))
52
(dlen (string-length display-title))
53
(start (+ x 2)))
54
(grid-set! grid (- start 1) y
55
(make-cell #\space color-default color-default attr-none))
+54
(grid-set! grid (- start 1) y #\space color-default color-default attr-none)
55
(let loop ((i 0))
56
(when (< i dlen)
57
(grid-set! grid (+ start i) y
59
(make-cell (string-ref display-title i)
60
color-default color-default attr-bold))
+58
(string-ref display-title i)
+59
color-default color-default attr-bold)
60
(loop (+ i 1))))
62
(grid-set! grid (+ start dlen) y
63
(make-cell #\space color-default color-default attr-none))))
+61
(grid-set! grid (+ start dlen) y #\space color-default color-default attr-none)))
62
63
;; Side borders
64
(let loop ((r (+ y 1)))
65
(when (< r (+ y h -1))
68
(grid-set! grid x r (make-cell box-v color-default color-default attr-none))
+66
(grid-set! grid x r box-v color-default color-default attr-none)
67
(when (> w 1)
70
(grid-set! grid (+ x w -1) r (make-cell box-v color-default color-default attr-none)))
+68
(grid-set! grid (+ x w -1) r box-v color-default color-default attr-none))
69
(loop (+ r 1))))
70
71
;; Bottom border
72
(when (> h 1)
75
(grid-set! grid x (+ y h -1) (make-cell box-bl color-default color-default attr-none))
+73
(grid-set! grid x (+ y h -1) box-bl color-default color-default attr-none)
74
(let loop ((c (+ x 1)))
75
(when (< c (+ x w -1))
78
(grid-set! grid c (+ y h -1) (make-cell box-h color-default color-default attr-none))
+76
(grid-set! grid c (+ y h -1) box-h color-default color-default attr-none)
77
(loop (+ c 1))))
78
(when (> w 1)
79
(grid-set! grid (+ x w -1) (+ y h -1)
82
(make-cell box-br color-default color-default attr-none))))))
+80
box-br color-default color-default attr-none)))))
81
82
;; Render text lines
83
;; Each line is (string fg bg attrs) or just a string
@@ -127,8 +125,7 @@
125
(when is-selected
126
(let pad ((c (+ x slen)))
127
(when (< c (+ x w))
130
(grid-set! grid c (+ y row)
131
(make-cell #\space fg bg attrs))
+128
(grid-set! grid c (+ y row) #\space fg bg attrs)
129
(pad (+ c 1))))))
130
(loop (+ row 1) (+ idx 1))))))
131
@@ -152,7 +149,7 @@
149
;; Fill entire row with inverse
150
(let loop ((c x))
151
(when (< c (+ x w))
155
(grid-set! grid c y (make-cell #\space color-default color-default attr-inverse))
+152
(grid-set! grid c y #\space color-default color-default attr-inverse)
153
(loop (+ c 1))))
154
;; Left text
155
(grid-write-string! grid x y left color-default color-default attr-inverse)
@@ -183,7 +180,7 @@
180
;; Clear remaining space
181
(let loop ((c (+ x plen (string-length visible-val))))
182
(when (< c (+ x w))
186
(grid-set! grid c y (make-cell #\space color-default color-default attr-none))
+183
(grid-set! grid c y #\space color-default color-default attr-none)
184
(loop (+ c 1)))))))
185
186
;; ============================================================
src/sigil/tui/grid.sglmodified
@@ -182,19 +182,29 @@
182
(: vector? integer? integer? -> vector?)
183
(vector-ref (grid-cells grid) (grid-index grid col row)))
184
185
;;; Set the cell at (col, row).
186
(define (grid-set! grid col row cell)
187
(: vector? integer? integer? vector? -> void?)
188
(vector-set! (grid-cells grid) (grid-index grid col row) cell))
189
190
;;; Clear the grid to default cells.
+185
;;; Set a cell at (col, row) by mutating it in place.
+186
;;;
+187
;;; Avoids allocating a new cell vector on every write.
+188
(define (grid-set! grid col row ch fg bg attrs)
+189
(: vector? integer? integer? char? integer? integer? integer? -> void?)
+190
(let ((cell (vector-ref (grid-cells grid) (grid-index grid col row))))
+191
(vector-set! cell 0 ch)
+192
(vector-set! cell 1 fg)
+193
(vector-set! cell 2 bg)
+194
(vector-set! cell 3 attrs)))
+195
+196
;;; Clear the grid to default cells (space, default colors, no attrs).
197
(define (grid-clear! grid)
198
(: vector? -> void?)
199
(let ((cells (grid-cells grid))
200
(size (* (grid-width grid) (grid-height grid))))
201
(let loop ((i 0))
202
(when (< i size)
197
(vector-set! cells i (default-cell))
+203
(let ((cell (vector-ref cells i)))
+204
(vector-set! cell 0 #\space)
+205
(vector-set! cell 1 -1)
+206
(vector-set! cell 2 -1)
+207
(vector-set! cell 3 0))
208
(loop (+ i 1))))))
209
210
;;; Write a string to the grid at (col, row) with the given style.
@@ -203,26 +213,37 @@
213
(define (grid-write-string! grid col row str fg bg attrs)
214
(: vector? integer? integer? string? integer? integer? integer? -> void?)
215
(let ((w (grid-width grid))
+216
(cells (grid-cells grid))
+217
(gw (grid-width grid))
218
(len (string-length str)))
219
(let loop ((i 0) (c col))
220
(when (and (< i len) (< c w))
209
(grid-set! grid c row (make-cell (string-ref str i) fg bg attrs))
+221
(let ((cell (vector-ref cells (+ (* row gw) c))))
+222
(vector-set! cell 0 (string-ref str i))
+223
(vector-set! cell 1 fg)
+224
(vector-set! cell 2 bg)
+225
(vector-set! cell 3 attrs))
226
(loop (+ i 1) (+ c 1))))))
227
212
;;; Fill a rectangle with a cell.
213
(define (grid-fill-rect! grid x y w h cell)
214
(: vector? integer? integer? integer? integer? vector? -> void?)
215
(let ((gw (grid-width grid))
+228
;;; Fill a rectangle with a character and style.
+229
(define (grid-fill-rect! grid x y w h ch fg bg attrs)
+230
(: vector? integer? integer? integer? integer? char? integer? integer? integer? -> void?)
+231
(let ((cells (grid-cells grid))
+232
(gw (grid-width grid))
233
(gh (grid-height grid)))
234
(let row-loop ((r y))
235
(when (and (< r (+ y h)) (< r gh))
236
(let col-loop ((c x))
237
(when (and (< c (+ x w)) (< c gw))
221
(grid-set! grid c r cell)
+238
(let ((cell (vector-ref cells (+ (* r gw) c))))
+239
(vector-set! cell 0 ch)
+240
(vector-set! cell 1 fg)
+241
(vector-set! cell 2 bg)
+242
(vector-set! cell 3 attrs))
243
(col-loop (+ c 1))))
244
(row-loop (+ r 1))))))
245
225
;;; Create a deep copy of a grid.
+246
;;; Create a deep copy of a grid (copies cell contents, not references).
247
(define (grid-copy grid)
248
(: vector? -> vector?)
249
(let* ((w (grid-width grid))
@@ -335,6 +356,8 @@
356
(: vector? vector? -> string?)
357
(let ((w (grid-width curr))
358
(h (grid-height curr))
+359
(prev-cells (grid-cells prev))
+360
(curr-cells (grid-cells curr))
361
(out (open-output-string))
362
(last-fg -2) ;; -2 = no style emitted yet (different from -1=default)
363
(last-bg -2)
@@ -345,13 +368,17 @@
368
(when (< row h)
369
(let col-loop ((col 0))
370
(when (< col w)
348
(let ((pc (grid-ref prev col row))
349
(cc (grid-ref curr col row)))
350
(when (not (cell=? pc cc))
351
(let ((fg (cell-fg cc))
352
(bg (cell-bg cc))
353
(attrs (cell-attrs cc))
354
(ch (cell-char cc)))
+371
(let* ((idx (+ (* row w) col))
+372
(pc (vector-ref prev-cells idx))
+373
(cc (vector-ref curr-cells idx)))
+374
(when (not (and (eqv? (vector-ref pc 0) (vector-ref cc 0))
+375
(eqv? (vector-ref pc 1) (vector-ref cc 1))
+376
(eqv? (vector-ref pc 2) (vector-ref cc 2))
+377
(eqv? (vector-ref pc 3) (vector-ref cc 3))))
+378
(let ((fg (vector-ref cc 1))
+379
(bg (vector-ref cc 2))
+380
(attrs (vector-ref cc 3))
+381
(ch (vector-ref cc 0)))
382
;; Move cursor if not at expected position
383
(when (or (not (= cursor-row row))
384
(not (= cursor-col col)))
test/test-grid.sglmodified
@@ -33,9 +33,8 @@
33
34
(test-group "read/write"
35
(test "grid-set! and grid-ref"
36
(let ((g (make-grid 10 5))
37
(c (make-cell #\X color-green color-default attr-bold)))
38
(grid-set! g 3 2 c)
+36
(let ((g (make-grid 10 5)))
+37
(grid-set! g 3 2 #\X color-green color-default attr-bold)
38
(assert-equal #\X (cell-char (grid-ref g 3 2)))
39
(assert-equal color-green (cell-fg (grid-ref g 3 2)))))
40
@@ -60,9 +59,8 @@
59
60
(test-group "fill-rect"
61
(test "grid-fill-rect! fills area"
63
(let ((g (make-grid 10 5))
64
(c (make-cell #\# color-yellow color-default attr-none)))
65
(grid-fill-rect! g 1 1 3 2 c)
+62
(let ((g (make-grid 10 5)))
+63
(grid-fill-rect! g 1 1 3 2 #\# color-yellow color-default attr-none)
64
;; Inside the rect
65
(assert-equal #\# (cell-char (grid-ref g 1 1)))
66
(assert-equal #\# (cell-char (grid-ref g 3 2)))
@@ -87,7 +85,7 @@
85
(test "single cell change"
86
(let ((prev (make-grid 5 3))
87
(curr (make-grid 5 3)))
90
(grid-set! curr 2 1 (make-cell #\X color-red color-default attr-none))
+88
(grid-set! curr 2 1 #\X color-red color-default attr-none)
89
(let ((diff (grid-diff prev curr)))
90
;; Should contain cursor move, SGR, and the character
91
(assert-true (> (string-length diff) 0))
@@ -100,14 +98,13 @@
98
(grid-write-string! curr 2 1 "AB" color-green color-default attr-none)
99
(let ((diff (grid-diff prev curr)))
100
;; Should have one cursor move, not two
103
;; Count ESC [ sequences — should have: 1 cursor move + 1 SGR + 1 reset = 3
101
(assert-true (> (string-length diff) 0)))))
102
103
(test "style changes emit new SGR"
104
(let ((prev (make-grid 10 3))
105
(curr (make-grid 10 3)))
109
(grid-set! curr 0 0 (make-cell #\A color-red color-default attr-none))
110
(grid-set! curr 1 0 (make-cell #\B color-blue color-default attr-none))
+106
(grid-set! curr 0 0 #\A color-red color-default attr-none)
+107
(grid-set! curr 1 0 #\B color-blue color-default attr-none)
108
(let ((diff (grid-diff prev curr)))
109
;; Two different styles means two SGR sequences
110
(assert-true (> (string-length diff) 10))))))
@@ -116,7 +113,7 @@
113
(test "copy creates independent grid"
114
(let* ((orig (make-grid 5 3))
115
(copy (grid-copy orig)))
119
(grid-set! copy 0 0 (make-cell #\X color-red color-default attr-none))
+116
(grid-set! copy 0 0 #\X color-red color-default attr-none)
117
;; Original should be unchanged
118
(assert-equal #\space (cell-char (grid-ref orig 0 0)))
119
;; Copy should have the change