diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1c334dbae21..7142960c4f9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -2,7 +2,7 @@
"version": "0.2.0",
"configurations": [
{
- "name": "Launch Debug (Windows)",
+ "name": "Launch edit (Windows)",
"preLaunchTask": "rust: cargo build",
"type": "cppvsdbg",
"request": "launch",
@@ -14,7 +14,7 @@
],
},
{
- "name": "Launch Debug (GDB)",
+ "name": "Launch edit (GDB, Linux)",
"preLaunchTask": "rust: cargo build",
"type": "cppdbg",
"request": "launch",
@@ -27,15 +27,28 @@
],
},
{
- "name": "Launch Debug (LLDB)",
+ // NOTE for macOS: In order for this task to work you have to:
+ // 1. Run the "Fix externalConsole on macOS" task once
+ // 2. Add the following to your VS Code settings:
+ // "lldb-dap.environment": {
+ // "LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY": "YES"
+ // }
+ "name": "Launch edit (lldb-dap, macOS)",
"preLaunchTask": "rust: cargo build",
- "type": "lldb",
+ "type": "lldb-dap",
"request": "launch",
"program": "${workspaceFolder}/target/debug/edit",
"cwd": "${workspaceFolder}",
"args": [
"${workspaceFolder}/crates/edit/src/bin/edit/main.rs"
],
- }
+ },
+ {
+ // This is a workaround for https://github.com/microsoft/vscode-cpptools/issues/5079
+ "name": "Fix externalConsole on macOS",
+ "type": "node-terminal",
+ "request": "launch",
+ "command": "osascript -e 'tell application \"Terminal\"\ndo script \"echo hello\"\nend tell'"
+ },
]
}
diff --git a/crates/edit/Cargo.toml b/crates/edit/Cargo.toml
index 90c47b6c882..f96ceb7d531 100644
--- a/crates/edit/Cargo.toml
+++ b/crates/edit/Cargo.toml
@@ -15,6 +15,7 @@ name = "lib"
harness = false
[features]
+# Display editor latency in the top-right corner
debug-latency = []
[dependencies]
diff --git a/crates/edit/src/bin/edit/main.rs b/crates/edit/src/bin/edit/main.rs
index b374e0ade4c..7e02ec82b41 100644
--- a/crates/edit/src/bin/edit/main.rs
+++ b/crates/edit/src/bin/edit/main.rs
@@ -12,8 +12,6 @@ mod localization;
mod state;
use std::borrow::Cow;
-#[cfg(feature = "debug-latency")]
-use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::time::Duration;
use std::{env, process};
@@ -23,7 +21,7 @@ use draw_filepicker::*;
use draw_menubar::*;
use draw_statusbar::*;
use edit::framebuffer::{self, IndexedColor};
-use edit::helpers::{CoordType, KIBI, MEBI, MetricFormatter, Rect, Size};
+use edit::helpers::*;
use edit::input::{self, kbmod, vk};
use edit::oklab::StraightRgba;
use edit::tui::*;
@@ -178,6 +176,8 @@ fn run() -> apperr::Result<()> {
#[cfg(feature = "debug-latency")]
{
+ use std::fmt::Write as _;
+
// Print the number of passes and latency in the top right corner.
let time_end = std::time::Instant::now();
let status = time_end - time_beg;
diff --git a/crates/edit/src/framebuffer.rs b/crates/edit/src/framebuffer.rs
index b02f867b95f..f2225e15edd 100644
--- a/crates/edit/src/framebuffer.rs
+++ b/crates/edit/src/framebuffer.rs
@@ -107,6 +107,8 @@ pub struct Framebuffer {
/// of the palette as [dark, light], unless the palette is recognized
/// as a light them, in which case it swaps them.
auto_colors: [StraightRgba; 2],
+ /// Above this lightness value, we consider a color to be "light".
+ auto_color_threshold: f32,
/// A cache table for previously contrasted colors.
/// See:
contrast_colors: [Cell<(StraightRgba, StraightRgba)>; CACHE_TABLE_SIZE],
@@ -125,6 +127,7 @@ impl Framebuffer {
DEFAULT_THEME[IndexedColor::Black as usize],
DEFAULT_THEME[IndexedColor::BrightWhite as usize],
],
+ auto_color_threshold: 0.5,
contrast_colors: [const { Cell::new((StraightRgba::zero(), StraightRgba::zero())) };
CACHE_TABLE_SIZE],
background_fill: DEFAULT_THEME[IndexedColor::Background as usize],
@@ -145,7 +148,17 @@ impl Framebuffer {
self.indexed_colors[IndexedColor::Black as usize],
self.indexed_colors[IndexedColor::BrightWhite as usize],
];
- if !Self::is_dark(self.auto_colors[0]) {
+
+ // It's not guaranteed that Black is actually dark and BrightWhite light (vice versa for a light theme).
+ // Such is the case with macOS 26's "Clear Dark" theme (and probably a lot other themes).
+ // Its black is #35424C (l=0.3716; oof!) and bright white is #E5EFF5 (l=0.9464).
+ // If we have a color such as #43698A (l=0.5065), which is l>0.5 ("light") and need a contrasting color,
+ // we need that to be #E5EFF5, even though that's also l>0.5. With a midpoint of 0.659, we get that right.
+ let lightness = self.auto_colors.map(|c| c.as_oklab().lightness());
+ self.auto_color_threshold = (lightness[0] + lightness[1]) * 0.5;
+
+ // Ensure [0] is dark and [1] is light.
+ if lightness[0] > lightness[1] {
self.auto_colors.swap(0, 1);
}
}
@@ -346,15 +359,12 @@ impl Framebuffer {
#[cold]
fn contrasted_slow(&self, color: StraightRgba) -> StraightRgba {
let idx = (color.to_ne() as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
- let contrast = self.auto_colors[Self::is_dark(color) as usize];
+ let is_dark = color.as_oklab().lightness() < self.auto_color_threshold;
+ let contrast = self.auto_colors[is_dark as usize];
self.contrast_colors[idx].set((color, contrast));
contrast
}
- fn is_dark(color: StraightRgba) -> bool {
- color.as_oklab().lightness() < 0.5
- }
-
/// Blends the given sRGB color onto the background bitmap.
///
/// TODO: The current approach blends foreground/background independently,
diff --git a/crates/edit/src/helpers.rs b/crates/edit/src/helpers.rs
index bd433588881..40590733da9 100644
--- a/crates/edit/src/helpers.rs
+++ b/crates/edit/src/helpers.rs
@@ -82,6 +82,9 @@ pub struct Size {
}
impl Size {
+ pub const MIN: Self = Self { width: 0, height: 0 };
+ pub const MAX: Self = Self { width: CoordType::MAX, height: CoordType::MAX };
+
pub fn as_rect(&self) -> Rect {
Rect { left: 0, top: 0, right: self.width, bottom: self.height }
}
diff --git a/crates/edit/src/sys/unix.rs b/crates/edit/src/sys/unix.rs
index 71b9edc7d74..07514325d5c 100644
--- a/crates/edit/src/sys/unix.rs
+++ b/crates/edit/src/sys/unix.rs
@@ -11,7 +11,7 @@ use std::fs::File;
use std::mem::{self, ManuallyDrop, MaybeUninit};
use std::os::fd::{AsRawFd as _, FromRawFd as _};
use std::path::Path;
-use std::ptr::{self, NonNull, null_mut};
+use std::ptr::{NonNull, null_mut};
use std::{thread, time};
use stdext::arena::{Arena, ArenaString, scratch_arena};
@@ -203,7 +203,7 @@ pub fn read_stdin(arena: &Arena, mut timeout: time::Duration) -> Option ArenaString<'a> {
}
}
+ #[must_use]
+ pub fn from_iter>(arena: &'a Arena, iter: T) -> Self {
+ let mut s = Self::new_in(arena);
+ s.extend(iter);
+ s
+ }
+
/// It's empty.
pub fn is_empty(&self) -> bool {
self.vec.is_empty()
@@ -284,6 +291,18 @@ impl fmt::Write for ArenaString<'_> {
}
}
+impl Extend for ArenaString<'_> {
+ fn extend>(&mut self, iter: I) {
+ let iterator = iter.into_iter();
+ let (lower_bound, _) = iterator.size_hint();
+ self.reserve(lower_bound);
+ iterator.for_each(move |c| self.push(c));
+ }
+
+ // TODO: This is where I'd put `extend_one` and `extend_reserve` impls, *but as always*,
+ // essential stdlib functions are unstable and that means we can't have them.
+}
+
#[macro_export]
macro_rules! arena_format {
($arena:expr, $($arg:tt)*) => {{