@@ -2,6 +2,28 @@ import { RGBA } from "@opentui/core"
22
33export namespace Terminal {
44 export type Colors = Awaited < ReturnType < typeof colors > >
5+
6+ function parse ( color : string ) : RGBA | null {
7+ if ( color . startsWith ( "rgb:" ) ) {
8+ const parts = color . substring ( 4 ) . split ( "/" )
9+ return RGBA . fromInts ( parseInt ( parts [ 0 ] , 16 ) >> 8 , parseInt ( parts [ 1 ] , 16 ) >> 8 , parseInt ( parts [ 2 ] , 16 ) >> 8 , 255 )
10+ }
11+ if ( color . startsWith ( "#" ) ) {
12+ return RGBA . fromHex ( color )
13+ }
14+ if ( color . startsWith ( "rgb(" ) ) {
15+ const parts = color . substring ( 4 , color . length - 1 ) . split ( "," )
16+ return RGBA . fromInts ( parseInt ( parts [ 0 ] ) , parseInt ( parts [ 1 ] ) , parseInt ( parts [ 2 ] ) , 255 )
17+ }
18+ return null
19+ }
20+
21+ function mode ( bg : RGBA | null ) : "dark" | "light" {
22+ if ( ! bg ) return "dark"
23+ const luminance = ( 0.299 * bg . r + 0.587 * bg . g + 0.114 * bg . b ) / 255
24+ return luminance > 0.5 ? "light" : "dark"
25+ }
26+
527 /**
628 * Query terminal colors including background, foreground, and palette (0-15).
729 * Uses OSC escape sequences to retrieve actual terminal color values.
@@ -31,46 +53,26 @@ export namespace Terminal {
3153 clearTimeout ( timeout )
3254 }
3355
34- const parseColor = ( colorStr : string ) : RGBA | null => {
35- if ( colorStr . startsWith ( "rgb:" ) ) {
36- const parts = colorStr . substring ( 4 ) . split ( "/" )
37- return RGBA . fromInts (
38- parseInt ( parts [ 0 ] , 16 ) >> 8 , // Convert 16-bit to 8-bit
39- parseInt ( parts [ 1 ] , 16 ) >> 8 ,
40- parseInt ( parts [ 2 ] , 16 ) >> 8 ,
41- 255 ,
42- )
43- }
44- if ( colorStr . startsWith ( "#" ) ) {
45- return RGBA . fromHex ( colorStr )
46- }
47- if ( colorStr . startsWith ( "rgb(" ) ) {
48- const parts = colorStr . substring ( 4 , colorStr . length - 1 ) . split ( "," )
49- return RGBA . fromInts ( parseInt ( parts [ 0 ] ) , parseInt ( parts [ 1 ] ) , parseInt ( parts [ 2 ] ) , 255 )
50- }
51- return null
52- }
53-
5456 const handler = ( data : Buffer ) => {
5557 const str = data . toString ( )
5658
5759 // Match OSC 11 (background color)
5860 const bgMatch = str . match ( / \x1b ] 1 1 ; ( [ ^ \x07 \x1b ] + ) / )
5961 if ( bgMatch ) {
60- background = parseColor ( bgMatch [ 1 ] )
62+ background = parse ( bgMatch [ 1 ] )
6163 }
6264
6365 // Match OSC 10 (foreground color)
6466 const fgMatch = str . match ( / \x1b ] 1 0 ; ( [ ^ \x07 \x1b ] + ) / )
6567 if ( fgMatch ) {
66- foreground = parseColor ( fgMatch [ 1 ] )
68+ foreground = parse ( fgMatch [ 1 ] )
6769 }
6870
6971 // Match OSC 4 (palette colors)
7072 const paletteMatches = str . matchAll ( / \x1b ] 4 ; ( \d + ) ; ( [ ^ \x07 \x1b ] + ) / g)
7173 for ( const match of paletteMatches ) {
7274 const index = parseInt ( match [ 1 ] )
73- const color = parseColor ( match [ 2 ] )
75+ const color = parse ( match [ 2 ] )
7476 if ( color ) paletteColors [ index ] = color
7577 }
7678
@@ -100,15 +102,36 @@ export namespace Terminal {
100102 } )
101103 }
102104
105+ // Keep startup mode detection separate from `colors()`: the TUI boot path only
106+ // needs OSC 11 and should resolve on the first background response instead of
107+ // waiting on the full palette query used by system theme generation.
103108 export async function getTerminalBackgroundColor ( ) : Promise < "dark" | "light" > {
104- const result = await colors ( )
105- if ( ! result . background ) return "dark"
109+ if ( ! process . stdin . isTTY ) return "dark"
106110
107- const { r, g, b } = result . background
108- // Calculate luminance using relative luminance formula
109- const luminance = ( 0.299 * r + 0.587 * g + 0.114 * b ) / 255
111+ return new Promise ( ( resolve ) => {
112+ let timeout : NodeJS . Timeout
110113
111- // Determine if dark or light based on luminance threshold
112- return luminance > 0.5 ? "light" : "dark"
114+ const cleanup = ( ) => {
115+ process . stdin . setRawMode ( false )
116+ process . stdin . removeListener ( "data" , handler )
117+ clearTimeout ( timeout )
118+ }
119+
120+ const handler = ( data : Buffer ) => {
121+ const match = data . toString ( ) . match ( / \x1b ] 1 1 ; ( [ ^ \x07 \x1b ] + ) / )
122+ if ( ! match ) return
123+ cleanup ( )
124+ resolve ( mode ( parse ( match [ 1 ] ) ) )
125+ }
126+
127+ process . stdin . setRawMode ( true )
128+ process . stdin . on ( "data" , handler )
129+ process . stdout . write ( "\x1b]11;?\x07" )
130+
131+ timeout = setTimeout ( ( ) => {
132+ cleanup ( )
133+ resolve ( "dark" )
134+ } , 1000 )
135+ } )
113136 }
114137}
0 commit comments