33 *
44 * Shows the region lifecycle:
55 * 1. Allocate space with raw newlines
6- * 2. DSR — queries cursor position to compute `top`
6+ * 2. Device Status Report ( DSR) — queries cursor position to compute `top`
77 * 3. CUP mode (all frames) — renders at `top`
88 * 4. Commit — restore cursor past region, advance with \n
99 */
1010
11- import { main , type Operation , sleep , until } from "effection" ;
11+ import { Buffer } from "node:buffer" ;
12+ import { readSync } from "node:fs" ;
13+ import process from "node:process" ;
14+ import { ensure , main , type Operation , sleep , until } from "effection" ;
1215import {
1316 close ,
1417 createInput ,
@@ -29,7 +32,7 @@ import { cursor, settings } from "../../settings.ts";
2932import { validated } from "../../validate.ts" ;
3033
3134const encode = ( s : string ) => new TextEncoder ( ) . encode ( s ) ;
32- const write = ( b : Uint8Array ) => Deno . stdout . writeSync ( b ) ;
35+ const write = ( b : Uint8Array ) => process . stdout . write ( Buffer . from ( b ) ) ;
3336
3437const GREEN = rgba ( 80 , 250 , 123 ) ;
3538const GRAY = rgba ( 100 , 100 , 100 ) ;
@@ -45,114 +48,19 @@ const RAINBOW = [RED, ORANGE, YELLOW, NGREEN, BLUE, VIOLET];
4548
4649const BRAILLE = [ "⠋" , "⠙" , "⠹" , "⠸" , "⠼" , "⠴" , "⠦" , "⠧" , "⠇" , "⠏" ] ;
4750
48- function * queryCursor ( ) : Operation < CursorEvent > {
49- let parser = yield * until ( createInput ( { escLatency : 100 } ) ) ;
50- write ( DSR ( ) ) ;
51-
52- let buf = new Uint8Array ( 32 ) ;
53- while ( true ) {
54- let n = Deno . stdin . readSync ( buf ) ;
55- if ( n === null ) continue ;
56- let result = parser . scan ( buf . subarray ( 0 , n ) ) ;
57- for ( let ev of result . events ) {
58- if ( ev . type === "cursor" ) {
59- return ev ;
60- }
61- }
62- }
63- }
64-
65- function waitKey ( ) {
66- let buf = new Uint8Array ( 32 ) ;
67- while ( true ) {
68- let n = Deno . stdin . readSync ( buf ) ;
69- if ( n === null ) continue ;
70- for ( let i = 0 ; i < n ; i ++ ) {
71- if ( buf [ i ] === 0x03 ) {
72- Deno . stdin . setRaw ( false ) ;
73- write ( SHOWCURSOR ( ) ) ;
74- Deno . exit ( 0 ) ;
75- }
76- }
77- return ;
78- }
79- }
80-
81- function box ( msg : string , fg : number , border : number ) : Op [ ] {
82- return [
83- open ( "root" , {
84- layout : { width : grow ( ) , height : grow ( ) , direction : "ttb" } ,
85- } ) ,
86- open ( "box" , {
87- layout : {
88- width : grow ( ) ,
89- height : grow ( ) ,
90- direction : "ttb" ,
91- padding : { left : 1 } ,
92- alignY : 2 ,
93- } ,
94- border : {
95- color : border ,
96- left : 1 ,
97- right : 1 ,
98- top : 1 ,
99- bottom : 1 ,
100- } ,
101- cornerRadius : { tl : 1 , tr : 1 , bl : 1 , br : 1 } ,
102- } ) ,
103- text ( msg , { color : fg } ) ,
104- close ( ) ,
105- close ( ) ,
106- ] ;
107- }
108-
109- function * transaction (
110- height : number ,
111- renderFrame : ( frame : number ) => Op [ ] ,
112- frames : number ,
113- interval : number ,
114- ) : Operation < void > {
115- let { columns } = Deno . consoleSize ( ) ;
116-
117- write ( encode ( "\n" . repeat ( height ) ) ) ;
118-
119- let pos = yield * queryCursor ( ) ;
120- /** 1-based terminal row where the region starts */
121- let row = pos . row - height + 1 ;
122-
123- write ( ESC ( "7" ) ) ;
124- let tty = settings ( cursor ( false ) ) ;
125- write ( tty . apply ) ;
126-
127- let term = validated (
128- yield * until ( createTerm ( { width : columns , height } ) ) ,
129- ) ;
130- for ( let i = 0 ; i < frames ; i ++ ) {
131- let result = term . render ( renderFrame ( i ) , { row } ) ;
132- write ( new Uint8Array ( result . output ) ) ;
133- yield * sleep ( interval ) ;
134- }
135-
136- write ( tty . revert ) ;
137- write ( ESC ( "8" ) ) ;
138- write ( encode ( "\n" ) ) ;
139- }
140-
141- function say ( msg : string ) {
142- write ( encode ( msg + "\n" ) ) ;
143- }
144-
145- function pause ( ) {
146- waitKey ( ) ;
147- write ( encode ( "\n" ) ) ;
148- }
149-
15051await main ( function * ( ) {
151- let { columns } = Deno . consoleSize ( ) ;
152- Deno . stdin . setRaw ( true ) ;
52+ let { columns } = terminalSize ( ) ;
53+ setRawMode ( true ) ;
15354 let tty = settings ( cursor ( false ) ) ;
15455 write ( tty . apply ) ;
15556
57+ yield * ensure ( ( ) => {
58+ // SGR reset sequence
59+ setRawMode ( false ) ;
60+ write ( CSI ( "0m" ) ) ;
61+ write ( tty . revert ) ;
62+ } ) ;
63+
15664 // Introduction
15765 say ( "Clayterm can render entire scenes, but it can also render" ) ;
15866 say ( '"inline" for a streaming UI. This is useful for semi-interactive' ) ;
@@ -338,6 +246,145 @@ await main(function* () {
338246
339247 write ( CSI ( "0m" ) ) ;
340248 write ( encode ( "\n" ) ) ;
341- write ( tty . revert ) ;
342- Deno . stdin . setRaw ( false ) ;
343249} ) ;
250+
251+ function terminalSize ( ) : { columns : number ; rows : number } {
252+ return process . stdout . isTTY
253+ ? {
254+ columns : process . stdout . columns ?? 80 ,
255+ rows : process . stdout . rows ?? 24 ,
256+ }
257+ : { columns : 80 , rows : 24 } ;
258+ }
259+
260+ function setRawMode ( enabled : boolean ) : void {
261+ if ( process . stdin . isTTY && typeof process . stdin . setRawMode === "function" ) {
262+ process . stdin . setRawMode ( enabled ) ;
263+ }
264+ }
265+
266+ function * queryCursor ( ) : Operation < CursorEvent > {
267+ let parser = yield * until ( createInput ( { escLatency : 100 } ) ) ;
268+ write ( DSR ( ) ) ;
269+
270+ let buf = Buffer . allocUnsafe ( 32 ) ;
271+ while ( true ) {
272+ let n : number ;
273+ try {
274+ n = readSync ( process . stdin . fd , buf , 0 , buf . length , null ) ;
275+ } catch ( error ) {
276+ if (
277+ error && typeof error === "object" &&
278+ ( "code" in error && ( error . code === "EAGAIN" || error . code === "EINTR" ) )
279+ ) {
280+ continue ;
281+ }
282+ throw error ;
283+ }
284+
285+ if ( n === 0 ) continue ;
286+ let result = parser . scan ( buf . subarray ( 0 , n ) ) ;
287+ for ( let ev of result . events ) {
288+ if ( ev . type === "cursor" ) {
289+ return ev ;
290+ }
291+ }
292+ }
293+ }
294+
295+ function waitKey ( ) : void {
296+ let buf = Buffer . allocUnsafe ( 32 ) ;
297+ while ( true ) {
298+ let n : number ;
299+ try {
300+ n = readSync ( process . stdin . fd , buf , 0 , buf . length , null ) ;
301+ } catch ( error ) {
302+ if (
303+ error && typeof error === "object" &&
304+ ( "code" in error && ( error . code === "EAGAIN" || error . code === "EINTR" ) )
305+ ) {
306+ continue ;
307+ }
308+ throw error ;
309+ }
310+
311+ if ( n === 0 ) continue ;
312+ for ( let i = 0 ; i < n ; i ++ ) {
313+ if ( buf [ i ] === 0x03 ) {
314+ setRawMode ( false ) ;
315+ write ( SHOWCURSOR ( ) ) ;
316+ process . exit ( 0 ) ;
317+ }
318+ }
319+ return ;
320+ }
321+ }
322+
323+ function box ( msg : string , fg : number , border : number ) : Op [ ] {
324+ return [
325+ open ( "root" , {
326+ layout : { width : grow ( ) , height : grow ( ) , direction : "ttb" } ,
327+ } ) ,
328+ open ( "box" , {
329+ layout : {
330+ width : grow ( ) ,
331+ height : grow ( ) ,
332+ direction : "ttb" ,
333+ padding : { left : 1 } ,
334+ alignY : 2 ,
335+ } ,
336+ border : {
337+ color : border ,
338+ left : 1 ,
339+ right : 1 ,
340+ top : 1 ,
341+ bottom : 1 ,
342+ } ,
343+ cornerRadius : { tl : 1 , tr : 1 , bl : 1 , br : 1 } ,
344+ } ) ,
345+ text ( msg , { color : fg } ) ,
346+ close ( ) ,
347+ close ( ) ,
348+ ] ;
349+ }
350+
351+ function * transaction (
352+ height : number ,
353+ renderFrame : ( frame : number ) => Op [ ] ,
354+ frames : number ,
355+ interval : number ,
356+ ) : Operation < void > {
357+ let { columns } = terminalSize ( ) ;
358+
359+ write ( encode ( "\n" . repeat ( height ) ) ) ;
360+
361+ let pos = yield * queryCursor ( ) ;
362+ /** 1-based terminal row where the region starts */
363+ let row = pos . row - height + 1 ;
364+
365+ write ( ESC ( "7" ) ) ;
366+ let tty = settings ( cursor ( false ) ) ;
367+ write ( tty . apply ) ;
368+
369+ let term = validated (
370+ yield * until ( createTerm ( { width : columns , height } ) ) ,
371+ ) ;
372+ for ( let i = 0 ; i < frames ; i ++ ) {
373+ let result = term . render ( renderFrame ( i ) , { row } ) ;
374+ write ( new Uint8Array ( result . output ) ) ;
375+ yield * sleep ( interval ) ;
376+ }
377+
378+ write ( tty . revert ) ;
379+ write ( ESC ( "8" ) ) ;
380+ write ( encode ( "\n" ) ) ;
381+ }
382+
383+ function say ( msg : string ) {
384+ write ( encode ( msg + "\n" ) ) ;
385+ }
386+
387+ function pause ( ) : void {
388+ waitKey ( ) ;
389+ write ( encode ( "\n" ) ) ;
390+ }
0 commit comments