Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/bin/edit/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,24 @@ fn setup_terminal(tui: &mut Tui, state: &mut State, vt_parser: &mut vt::Parser)
tui.setup_indexed_colors(indexed_colors);
}

// Detects if edit is running on an "old" version of terminal.app on Mac OS. If it detects
// the terminal.app is older than version 460, which added truecolor support,
// is_old_macos_terminal gets set to true, and sets ColorMode to Color256.
let is_old_macos_terminal = {
if env::var("TERM_PROGRAM").as_deref() == Ok("Apple_Terminal") {
env::var("TERM_PROGRAM_VERSION")
.ok()
.and_then(|s| s.split('.').next()?.parse::<u32>().ok())
.map_or(false, |v| v < 460)
} else {
false
}
};

if is_old_macos_terminal {
tui.setup_color_mode(framebuffer::ColorMode::Color256);
}

RestoreModes
}

Expand Down
47 changes: 46 additions & 1 deletion src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ pub enum IndexedColor {
Foreground,
}

/// Color modes used in format_color function
#[derive(Clone, Copy, Default)]
pub enum ColorMode {
#[default]
TrueColor,
Color256,
}

/// Number of indices used by [`IndexedColor`].
pub const INDEXED_COLORS_COUNT: usize = 18;

Expand Down Expand Up @@ -105,6 +113,8 @@ pub struct Framebuffer {
contrast_colors: [Cell<(u32, u32)>; CACHE_TABLE_SIZE],
background_fill: u32,
foreground_fill: u32,
// The color mode, either truecolor(default) or color256
color_mode: ColorMode,
}

impl Framebuffer {
Expand All @@ -121,6 +131,7 @@ impl Framebuffer {
contrast_colors: [const { Cell::new((0, 0)) }; CACHE_TABLE_SIZE],
background_fill: DEFAULT_THEME[IndexedColor::Background as usize],
foreground_fill: DEFAULT_THEME[IndexedColor::Foreground as usize],
color_mode: ColorMode::default(),
}
}

Expand Down Expand Up @@ -537,6 +548,12 @@ impl Framebuffer {
result
}

// Sets the color rendering mode, either truecolor(default) or color256, determined by the
// setup_terminal function in main.rs
pub fn set_color_mode(&mut self, mode: ColorMode) {
self.color_mode = mode;
}

fn format_color(&self, dst: &mut ArenaString, fg: bool, mut color: u32) {
let typ = if fg { '3' } else { '4' };

Expand Down Expand Up @@ -567,7 +584,35 @@ impl Framebuffer {
let r = color & 0xff;
let g = (color >> 8) & 0xff;
let b = (color >> 16) & 0xff;
_ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");

// If the terminal doesn't support truecolor, the rgb channel needs to be
// downsampled to display properly.
match self.color_mode {
ColorMode::TrueColor => {
_ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");
}
ColorMode::Color256 => {
let index: u8 = if r == g && g == b {
Copy link
Member

@lhecker lhecker Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the grayscale detection is a little brittle. It may be better to convert the 216+24 colors to Oklab and store them in a list here. Then we can search linearly through it and find the closest coordinate and its index.

While writing a 4D matcher I found that Rust is entirely unable to optimize [f32; 4] operations from MOVSS to MOVPS under opt-level=s which is quite disappointing to learn. I suppose I should hurry my custom stdlib collection reimplementation so we can switch back to opt-level=3 sooner rather than later.
Edit: Filed as rust-lang/rust#143242

All that aside, I would prefer if the entire code is only compiled under cfg(target_platform = "macos").

// grayscale path
if r < 8 {
16
} else if r > 248 {
231
} else {
let gray_index = ((r - 8 + 5) / 10).min(23);
232 + gray_index
}
} else {
// Color path
let r_idx = (r * 6 / 256) as u32;
let g_idx = (g * 6 / 256) as u32;
let b_idx = (b * 6 / 256) as u32;
(16 + 36 * r_idx + 6 * g_idx + b_idx) as u32
} as u8;

_ = write!(dst, "\x1b[{typ}8;5;{index}m");
}
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ use crate::buffer::{CursorMovement, MoveLineDirection, RcTextBuffer, TextBuffer,
use crate::cell::*;
use crate::clipboard::Clipboard;
use crate::document::WriteableDocument;
use crate::framebuffer::{Attributes, Framebuffer, INDEXED_COLORS_COUNT, IndexedColor};
use crate::framebuffer::{Attributes, ColorMode, Framebuffer, INDEXED_COLORS_COUNT, IndexedColor};
use crate::hash::*;
use crate::helpers::*;
use crate::input::{InputKeyMod, kbmod, vk};
Expand Down Expand Up @@ -429,6 +429,11 @@ impl Tui {
self.framebuffer.set_indexed_colors(colors);
}

/// Sets the color rendering mode based on terminal capabilities.
pub fn setup_color_mode(&mut self, mode: ColorMode) {
self.framebuffer.set_color_mode(mode);
}

/// Set up translations for Ctrl/Alt/Shift modifiers.
pub fn setup_modifier_translations(&mut self, translations: ModifierTranslations) {
self.modifier_translations = translations;
Expand Down