11// deno-lint-ignore-file no-fallthrough
2+ import { Buffer } from "node:buffer" ;
3+ import process from "node:process" ;
24import {
35 createChannel ,
46 each ,
@@ -33,6 +35,54 @@ import {
3335import { useInput } from "./use-input.ts" ;
3436import { useStdin } from "./use-stdin.ts" ;
3537
38+ function terminalSize ( ) : { columns : number ; rows : number } {
39+ return process . stdout . isTTY
40+ ? {
41+ columns : process . stdout . columns ?? 80 ,
42+ rows : process . stdout . rows ?? 24 ,
43+ }
44+ : { columns : 80 , rows : 24 } ;
45+ }
46+
47+ function setRawMode ( enabled : boolean ) : void {
48+ if ( process . stdin . isTTY && typeof process . stdin . setRawMode === "function" ) {
49+ process . stdin . setRawMode ( enabled ) ;
50+ }
51+ }
52+
53+ function writeStdout ( bytes : Uint8Array ) : void {
54+ process . stdout . write ( Buffer . from ( bytes ) ) ;
55+ }
56+
57+ function equalBytes ( a : Uint8Array , b : Uint8Array ) : boolean {
58+ if ( a . length !== b . length ) {
59+ return false ;
60+ }
61+ for ( let i = 0 ; i < a . length ; i ++ ) {
62+ if ( a [ i ] !== b [ i ] ) {
63+ return false ;
64+ }
65+ }
66+ return true ;
67+ }
68+
69+ function equalSetting ( a : Setting , b : Setting ) : boolean {
70+ return equalBytes ( a . apply , b . apply ) && equalBytes ( a . revert , b . revert ) ;
71+ }
72+
73+ // Avoid rewriting terminal input modes on every mousemove. Deno's `node:` TTY
74+ // compatibility layer on Windows is sensitive to that churn even when the
75+ // effective settings are unchanged.
76+ function updateFlagsIfChanged ( current : Setting , next : Setting ) : Setting {
77+ if ( equalSetting ( current , next ) ) {
78+ return current ;
79+ }
80+
81+ writeStdout ( current . revert ) ;
82+ writeStdout ( next . apply ) ;
83+ return next ;
84+ }
85+
3686const active = rgba ( 60 , 120 , 220 ) ;
3787const inactive = rgba ( 50 , 50 , 60 ) ;
3888const on = rgba ( 40 , 180 , 80 ) ;
@@ -563,34 +613,33 @@ function ttyFlags(ctx: AppContext): Setting {
563613}
564614
565615await main ( function * ( ) {
566- let { columns, rows } = Deno . stdout . isTerminal ( )
567- ? Deno . consoleSize ( )
568- : { columns : 80 , rows : 24 } ;
616+ let { columns, rows } = terminalSize ( ) ;
569617
570- Deno . stdin . setRaw ( true ) ;
618+ setRawMode ( true ) ;
571619
572620 let stdin = yield * useStdin ( ) ;
573621 let input = useInput ( stdin ) ;
574622
575623 let term = yield * until ( createTerm ( { width : columns , height : rows } ) ) ;
576624
577625 let tty = settings ( alternateBuffer ( ) , cursor ( false ) ) ;
578- Deno . stdout . writeSync ( tty . apply ) ;
626+ writeStdout ( tty . apply ) ;
579627
580628 let modality = recognizer ( ) ;
581629 let context = modality . next ( ) . value ;
582630
583631 let flags = ttyFlags ( context ) ;
584- Deno . stdout . writeSync ( flags . apply ) ;
632+ writeStdout ( flags . apply ) ;
585633
586634 yield * ensure ( ( ) => {
587- Deno . stdout . writeSync ( flags . revert ) ;
588- Deno . stdout . writeSync ( tty . revert ) ;
635+ setRawMode ( false ) ;
636+ writeStdout ( flags . revert ) ;
637+ writeStdout ( tty . revert ) ;
589638 } ) ;
590639
591640 let { output } = term . render ( keyboard ( context ) ) ;
592641
593- Deno . stdout . writeSync ( output ) ;
642+ writeStdout ( output ) ;
594643
595644 let pointer = {
596645 events : createChannel < PointerEvent , void > ( ) ,
@@ -616,9 +665,7 @@ await main(function* () {
616665 context = { ...context , logged : prev } ;
617666 }
618667
619- Deno . stdout . writeSync ( flags . revert ) ;
620- flags = ttyFlags ( context ) ;
621- Deno . stdout . writeSync ( flags . apply ) ;
668+ flags = updateFlagsIfChanged ( flags , ttyFlags ( context ) ) ;
622669
623670 if ( context [ "Capture mouse events" ] ) {
624671 if ( "x" in event ) {
@@ -640,7 +687,7 @@ await main(function* () {
640687 yield * pointer . events . send ( event ) ;
641688 }
642689
643- Deno . stdout . writeSync ( output ) ;
690+ writeStdout ( output ) ;
644691
645692 yield * each . next ( ) ;
646693 }
0 commit comments