Skip to content

Commit dbd7465

Browse files
authored
Fix colors with Terminal.app's Clear Dark theme (#728)
1 parent fa2b1b8 commit dbd7465

7 files changed

Lines changed: 62 additions & 16 deletions

File tree

.vscode/launch.json

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5-
"name": "Launch Debug (Windows)",
5+
"name": "Launch edit (Windows)",
66
"preLaunchTask": "rust: cargo build",
77
"type": "cppvsdbg",
88
"request": "launch",
@@ -14,7 +14,7 @@
1414
],
1515
},
1616
{
17-
"name": "Launch Debug (GDB)",
17+
"name": "Launch edit (GDB, Linux)",
1818
"preLaunchTask": "rust: cargo build",
1919
"type": "cppdbg",
2020
"request": "launch",
@@ -27,15 +27,28 @@
2727
],
2828
},
2929
{
30-
"name": "Launch Debug (LLDB)",
30+
// NOTE for macOS: In order for this task to work you have to:
31+
// 1. Run the "Fix externalConsole on macOS" task once
32+
// 2. Add the following to your VS Code settings:
33+
// "lldb-dap.environment": {
34+
// "LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY": "YES"
35+
// }
36+
"name": "Launch edit (lldb-dap, macOS)",
3137
"preLaunchTask": "rust: cargo build",
32-
"type": "lldb",
38+
"type": "lldb-dap",
3339
"request": "launch",
3440
"program": "${workspaceFolder}/target/debug/edit",
3541
"cwd": "${workspaceFolder}",
3642
"args": [
3743
"${workspaceFolder}/crates/edit/src/bin/edit/main.rs"
3844
],
39-
}
45+
},
46+
{
47+
// This is a workaround for https://github.com/microsoft/vscode-cpptools/issues/5079
48+
"name": "Fix externalConsole on macOS",
49+
"type": "node-terminal",
50+
"request": "launch",
51+
"command": "osascript -e 'tell application \"Terminal\"\ndo script \"echo hello\"\nend tell'"
52+
},
4053
]
4154
}

crates/edit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ name = "lib"
1515
harness = false
1616

1717
[features]
18+
# Display editor latency in the top-right corner
1819
debug-latency = []
1920

2021
[dependencies]

crates/edit/src/bin/edit/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ mod localization;
1212
mod state;
1313

1414
use std::borrow::Cow;
15-
#[cfg(feature = "debug-latency")]
16-
use std::fmt::Write;
1715
use std::path::{Path, PathBuf};
1816
use std::time::Duration;
1917
use std::{env, process};
@@ -23,7 +21,7 @@ use draw_filepicker::*;
2321
use draw_menubar::*;
2422
use draw_statusbar::*;
2523
use edit::framebuffer::{self, IndexedColor};
26-
use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size};
24+
use edit::helpers::*;
2725
use edit::input::{self, kbmod, vk};
2826
use edit::oklab::StraightRgba;
2927
use edit::tui::*;
@@ -178,6 +176,8 @@ fn run() -> apperr::Result<()> {
178176

179177
#[cfg(feature = "debug-latency")]
180178
{
179+
use std::fmt::Write as _;
180+
181181
// Print the number of passes and latency in the top right corner.
182182
let time_end = std::time::Instant::now();
183183
let status = time_end - time_beg;

crates/edit/src/framebuffer.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ pub struct Framebuffer {
107107
/// of the palette as [dark, light], unless the palette is recognized
108108
/// as a light them, in which case it swaps them.
109109
auto_colors: [StraightRgba; 2],
110+
/// Above this lightness value, we consider a color to be "light".
111+
auto_color_threshold: f32,
110112
/// A cache table for previously contrasted colors.
111113
/// See: <https://fgiesen.wordpress.com/2019/02/11/cache-tables/>
112114
contrast_colors: [Cell<(StraightRgba, StraightRgba)>; CACHE_TABLE_SIZE],
@@ -125,6 +127,7 @@ impl Framebuffer {
125127
DEFAULT_THEME[IndexedColor::Black as usize],
126128
DEFAULT_THEME[IndexedColor::BrightWhite as usize],
127129
],
130+
auto_color_threshold: 0.5,
128131
contrast_colors: [const { Cell::new((StraightRgba::zero(), StraightRgba::zero())) };
129132
CACHE_TABLE_SIZE],
130133
background_fill: DEFAULT_THEME[IndexedColor::Background as usize],
@@ -145,7 +148,17 @@ impl Framebuffer {
145148
self.indexed_colors[IndexedColor::Black as usize],
146149
self.indexed_colors[IndexedColor::BrightWhite as usize],
147150
];
148-
if !Self::is_dark(self.auto_colors[0]) {
151+
152+
// It's not guaranteed that Black is actually dark and BrightWhite light (vice versa for a light theme).
153+
// Such is the case with macOS 26's "Clear Dark" theme (and probably a lot other themes).
154+
// Its black is #35424C (l=0.3716; oof!) and bright white is #E5EFF5 (l=0.9464).
155+
// If we have a color such as #43698A (l=0.5065), which is l>0.5 ("light") and need a contrasting color,
156+
// we need that to be #E5EFF5, even though that's also l>0.5. With a midpoint of 0.659, we get that right.
157+
let lightness = self.auto_colors.map(|c| c.as_oklab().lightness());
158+
self.auto_color_threshold = (lightness[0] + lightness[1]) * 0.5;
159+
160+
// Ensure [0] is dark and [1] is light.
161+
if lightness[0] > lightness[1] {
149162
self.auto_colors.swap(0, 1);
150163
}
151164
}
@@ -346,15 +359,12 @@ impl Framebuffer {
346359
#[cold]
347360
fn contrasted_slow(&self, color: StraightRgba) -> StraightRgba {
348361
let idx = (color.to_ne() as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
349-
let contrast = self.auto_colors[Self::is_dark(color) as usize];
362+
let is_dark = color.as_oklab().lightness() < self.auto_color_threshold;
363+
let contrast = self.auto_colors[is_dark as usize];
350364
self.contrast_colors[idx].set((color, contrast));
351365
contrast
352366
}
353367

354-
fn is_dark(color: StraightRgba) -> bool {
355-
color.as_oklab().lightness() < 0.5
356-
}
357-
358368
/// Blends the given sRGB color onto the background bitmap.
359369
///
360370
/// TODO: The current approach blends foreground/background independently,

crates/edit/src/helpers.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub struct Size {
8282
}
8383

8484
impl Size {
85+
pub const MIN: Self = Self { width: 0, height: 0 };
86+
pub const MAX: Self = Self { width: CoordType::MAX, height: CoordType::MAX };
87+
8588
pub fn as_rect(&self) -> Rect {
8689
Rect { left: 0, top: 0, right: self.width, bottom: self.height }
8790
}

crates/edit/src/sys/unix.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::fs::File;
1111
use std::mem::{self, ManuallyDrop, MaybeUninit};
1212
use std::os::fd::{AsRawFd as _, FromRawFd as _};
1313
use std::path::Path;
14-
use std::ptr::{self, NonNull, null_mut};
14+
use std::ptr::{NonNull, null_mut};
1515
use std::{thread, time};
1616

1717
use stdext::arena::{Arena, ArenaString, scratch_arena};
@@ -203,7 +203,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option<ArenaStr
203203
tv_sec: timeout.as_secs() as libc::time_t,
204204
tv_nsec: timeout.subsec_nanos() as libc::c_long,
205205
};
206-
ret = libc::ppoll(&mut pollfd, 1, &ts, ptr::null());
206+
ret = libc::ppoll(&mut pollfd, 1, &ts, std::ptr::null());
207207
}
208208
#[cfg(not(target_os = "linux"))]
209209
{

crates/stdext/src/arena/string.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ impl<'a> ArenaString<'a> {
9595
}
9696
}
9797

98+
#[must_use]
99+
pub fn from_iter<T: IntoIterator<Item = char>>(arena: &'a Arena, iter: T) -> Self {
100+
let mut s = Self::new_in(arena);
101+
s.extend(iter);
102+
s
103+
}
104+
98105
/// It's empty.
99106
pub fn is_empty(&self) -> bool {
100107
self.vec.is_empty()
@@ -284,6 +291,18 @@ impl fmt::Write for ArenaString<'_> {
284291
}
285292
}
286293

294+
impl Extend<char> for ArenaString<'_> {
295+
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
296+
let iterator = iter.into_iter();
297+
let (lower_bound, _) = iterator.size_hint();
298+
self.reserve(lower_bound);
299+
iterator.for_each(move |c| self.push(c));
300+
}
301+
302+
// TODO: This is where I'd put `extend_one` and `extend_reserve` impls, *but as always*,
303+
// essential stdlib functions are unstable and that means we can't have them.
304+
}
305+
287306
#[macro_export]
288307
macro_rules! arena_format {
289308
($arena:expr, $($arg:tt)*) => {{

0 commit comments

Comments
 (0)