|
fn setup_terminal(tui: &mut Tui, vt_parser: &mut vt::Parser) -> RestoreModes { |
|
sys::write_stdout(concat!( |
|
// 1049: Alternative Screen Buffer |
|
// I put the ASB switch in the beginning, just in case the terminal performs |
|
// some additional state tracking beyond the modes we enable/disable. |
|
// 1002: Cell Motion Mouse Tracking |
|
// 1006: SGR Mouse Mode |
|
// 2004: Bracketed Paste Mode |
|
"\x1b[?1049h\x1b[?1002;1006;2004h", |
|
// OSC 4 color table requests for indices 0 through 15 (base colors). |
|
"\x1b]4;0;?;1;?;2;?;3;?;4;?;5;?;6;?;7;?\x07", |
|
"\x1b]4;8;?;9;?;10;?;11;?;12;?;13;?;14;?;15;?\x07", |
|
// OSC 10 and 11 queries for the current foreground and background colors. |
|
"\x1b]10;?\x07\x1b]11;?\x07", |
|
// CSI c reports the terminal capabilities. |
|
// It also helps us to detect the end of the responses, because not all |
|
// terminals support the OSC queries, but all of them support CSI c. |
|
"\x1b[c", |
|
)); |
|
|
|
let mut done = false; |
|
let mut osc_buffer = String::new(); |
|
let mut indexed_colors = framebuffer::DEFAULT_THEME; |
|
let mut color_responses = 0; |
|
|
|
while !done { |
|
let scratch = scratch_arena(None); |
|
let Some(input) = sys::read_stdin(&scratch, vt_parser.read_timeout()) else { |
|
break; |
|
}; |
|
|
|
let mut vt_stream = vt_parser.parse(&input); |
|
while let Some(token) = vt_stream.next() { |
|
match token { |
|
Token::Csi(state) if state.final_byte == 'c' => done = true, |
|
Token::Osc { mut data, partial } => { |
|
if partial { |
|
osc_buffer.push_str(data); |
|
continue; |
|
} |
|
if !osc_buffer.is_empty() { |
|
osc_buffer.push_str(data); |
|
data = &osc_buffer; |
|
} |
|
|
|
let mut splits = data.split_terminator(';'); |
|
|
|
let color = match splits.next().unwrap_or("") { |
|
// The response is `4;<color>;rgb:<r>/<g>/<b>`. |
|
"4" => match splits.next().unwrap_or("").parse::<usize>() { |
|
Ok(val) if val < 16 => &mut indexed_colors[val], |
|
_ => continue, |
|
}, |
|
// The response is `10;rgb:<r>/<g>/<b>`. |
|
"10" => &mut indexed_colors[IndexedColor::Foreground as usize], |
|
// The response is `11;rgb:<r>/<g>/<b>`. |
|
"11" => &mut indexed_colors[IndexedColor::Background as usize], |
|
_ => continue, |
|
}; |
|
|
|
let color_param = splits.next().unwrap_or(""); |
|
if !color_param.starts_with("rgb:") { |
|
continue; |
|
} |
|
|
|
let mut iter = color_param[4..].split_terminator('/'); |
|
let rgb_parts = [(); 3].map(|_| iter.next().unwrap_or("0")); |
|
let mut rgb = 0; |
|
|
|
for part in rgb_parts { |
|
if part.len() == 2 || part.len() == 4 { |
|
let Ok(mut val) = usize::from_str_radix(part, 16) else { |
|
continue; |
|
}; |
|
if part.len() == 4 { |
|
// Round from 16 bits to 8 bits. |
|
val = (val * 0xff + 0x7fff) / 0xffff; |
|
} |
|
rgb = (rgb >> 8) | ((val as u32) << 16); |
|
} |
|
} |
|
|
|
*color = rgb | 0xff000000; |
|
color_responses += 1; |
|
osc_buffer.clear(); |
|
} |
|
_ => {} |
|
} |
|
} |
|
} |
|
|
|
if color_responses == indexed_colors.len() { |
|
tui.setup_indexed_colors(indexed_colors); |
|
} |
|
|
|
RestoreModes |
|
} |
We currently expect full RGB support from the hosting terminal. Terminal.app is likely the only popular terminal that doesn't support it, but it's very popular (relatively speaking), so we should add support for it. This requires downsampling the color here:
edit/src/framebuffer.rs
Lines 524 to 555 in b008570
And somehow detecting RGB support here:
edit/src/bin/edit/main.rs
Lines 499 to 595 in b008570
Detecting RGB support should not use the
COLORTERMenvironment variable, because a) I don't like it and b) I'm quite certain there's alternatives to do it. The simplest alternative is to just assume that Terminal.app is the only terminal that doesn't support it (...given that this project doesn't aim to support old terminals).