forked from jess/Acord
Colors... We'll need a new space.
This commit is contained in:
parent
931b8bf0b6
commit
ccffb9149d
|
|
@ -589,12 +589,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuItemValidation {
|
|||
let mode = ConfigManager.shared.themeMode
|
||||
let name: String
|
||||
switch mode {
|
||||
case "dark": name = "mocha"
|
||||
case "dark": name = "kicad"
|
||||
case "light": name = "latte"
|
||||
default:
|
||||
let appearance = NSApp.effectiveAppearance
|
||||
let isDark = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
|
||||
name = isDark ? "mocha" : "latte"
|
||||
name = isDark ? "kicad" : "latte"
|
||||
}
|
||||
viewport?.setTheme(name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,35 @@ struct Theme {
|
|||
rosewater: NSColor(red: 0.961, green: 0.761, blue: 0.765, alpha: 1)
|
||||
)
|
||||
|
||||
static let kicad = CatppuccinPalette(
|
||||
base: NSColor(red: 0.090, green: 0.094, blue: 0.114, alpha: 1),
|
||||
mantle: NSColor(red: 0.075, green: 0.078, blue: 0.102, alpha: 1),
|
||||
crust: NSColor(red: 0.059, green: 0.059, blue: 0.059, alpha: 1),
|
||||
surface0: NSColor(red: 0.102, green: 0.110, blue: 0.125, alpha: 1),
|
||||
surface1: NSColor(red: 0.122, green: 0.126, blue: 0.141, alpha: 1),
|
||||
surface2: NSColor(red: 0.133, green: 0.141, blue: 0.149, alpha: 1),
|
||||
overlay0: NSColor(red: 0.361, green: 0.368, blue: 0.418, alpha: 1),
|
||||
overlay1: NSColor(red: 0.449, green: 0.453, blue: 0.499, alpha: 1),
|
||||
overlay2: NSColor(red: 0.548, green: 0.545, blue: 0.598, alpha: 1),
|
||||
text: NSColor(red: 0.965, green: 0.954, blue: 0.969, alpha: 1),
|
||||
subtext0: NSColor(red: 0.679, green: 0.668, blue: 0.725, alpha: 1),
|
||||
subtext1: NSColor(red: 0.824, green: 0.813, blue: 0.852, alpha: 1),
|
||||
red: NSColor(red: 0.914, green: 0.376, blue: 0.376, alpha: 1),
|
||||
maroon: NSColor(red: 0.949, green: 0.416, blue: 0.584, alpha: 1),
|
||||
peach: NSColor(red: 0.965, green: 0.533, blue: 0.404, alpha: 1),
|
||||
yellow: NSColor(red: 0.988, green: 0.831, blue: 0.349, alpha: 1),
|
||||
green: NSColor(red: 0.403, green: 0.972, blue: 0.534, alpha: 1),
|
||||
teal: NSColor(red: 0.310, green: 1.000, blue: 0.882, alpha: 1),
|
||||
sky: NSColor(red: 0.403, green: 0.813, blue: 0.972, alpha: 1),
|
||||
sapphire: NSColor(red: 0.384, green: 0.635, blue: 0.949, alpha: 1),
|
||||
blue: NSColor(red: 0.337, green: 0.475, blue: 0.988, alpha: 1),
|
||||
lavender: NSColor(red: 1.000, green: 0.718, blue: 0.937, alpha: 1),
|
||||
mauve: NSColor(red: 0.635, green: 0.282, blue: 0.980, alpha: 1),
|
||||
pink: NSColor(red: 0.973, green: 0.345, blue: 0.718, alpha: 1),
|
||||
flamingo: NSColor(red: 0.965, green: 0.533, blue: 0.404, alpha: 1),
|
||||
rosewater: NSColor(red: 0.984, green: 0.639, blue: 0.757, alpha: 1)
|
||||
)
|
||||
|
||||
static let latte = CatppuccinPalette(
|
||||
base: NSColor(red: 0.937, green: 0.929, blue: 0.961, alpha: 1),
|
||||
mantle: NSColor(red: 0.906, green: 0.898, blue: 0.941, alpha: 1),
|
||||
|
|
@ -92,12 +121,12 @@ struct Theme {
|
|||
static var current: CatppuccinPalette {
|
||||
let mode = ConfigManager.shared.themeMode
|
||||
switch mode {
|
||||
case "dark": return mocha
|
||||
case "dark": return kicad
|
||||
case "light": return latte
|
||||
default:
|
||||
let appearance = NSApp.effectiveAppearance
|
||||
let isDark = appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
|
||||
return isDark ? mocha : latte
|
||||
return isDark ? kicad : latte
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,38 @@ pub static MOCHA: Palette = Palette {
|
|||
crust: Color::from_rgb(0.067, 0.067, 0.106),
|
||||
};
|
||||
|
||||
/// KiCad-inspired dark — near-black background, saturated accents, high
|
||||
/// contrast. The signature KiCad schematic-editor feel: vivid greens,
|
||||
/// bright cyans, punchy reds and yellows on a deep navy base.
|
||||
pub static KICAD: Palette = Palette {
|
||||
rosewater: Color::from_rgb(0.984, 0.639, 0.757),
|
||||
flamingo: Color::from_rgb(0.965, 0.533, 0.404),
|
||||
pink: Color::from_rgb(0.973, 0.345, 0.718),
|
||||
mauve: Color::from_rgb(0.635, 0.282, 0.980),
|
||||
red: Color::from_rgb(0.914, 0.376, 0.376),
|
||||
maroon: Color::from_rgb(0.949, 0.416, 0.584),
|
||||
peach: Color::from_rgb(0.965, 0.533, 0.404),
|
||||
yellow: Color::from_rgb(0.988, 0.831, 0.349),
|
||||
green: Color::from_rgb(0.403, 0.972, 0.534),
|
||||
teal: Color::from_rgb(0.310, 1.000, 0.882),
|
||||
sky: Color::from_rgb(0.403, 0.813, 0.972),
|
||||
sapphire: Color::from_rgb(0.384, 0.635, 0.949),
|
||||
blue: Color::from_rgb(0.337, 0.475, 0.988),
|
||||
lavender: Color::from_rgb(1.000, 0.718, 0.937),
|
||||
text: Color::from_rgb(0.965, 0.954, 0.969),
|
||||
subtext1: Color::from_rgb(0.824, 0.813, 0.852),
|
||||
subtext0: Color::from_rgb(0.679, 0.668, 0.725),
|
||||
overlay2: Color::from_rgb(0.548, 0.545, 0.598),
|
||||
overlay1: Color::from_rgb(0.449, 0.453, 0.499),
|
||||
overlay0: Color::from_rgb(0.361, 0.368, 0.418),
|
||||
surface2: Color::from_rgb(0.133, 0.141, 0.149),
|
||||
surface1: Color::from_rgb(0.122, 0.126, 0.141),
|
||||
surface0: Color::from_rgb(0.102, 0.110, 0.125),
|
||||
base: Color::from_rgb(0.090, 0.094, 0.114),
|
||||
mantle: Color::from_rgb(0.075, 0.078, 0.102),
|
||||
crust: Color::from_rgb(0.059, 0.059, 0.059),
|
||||
};
|
||||
|
||||
pub static LATTE: Palette = Palette {
|
||||
rosewater: Color::from_rgb(0.863, 0.541, 0.471),
|
||||
flamingo: Color::from_rgb(0.867, 0.471, 0.471),
|
||||
|
|
@ -105,7 +137,8 @@ pub fn is_dark() -> bool {
|
|||
pub fn set_theme(name: &str) {
|
||||
let (pal, dark) = match name {
|
||||
"latte" | "light" => (&LATTE, false),
|
||||
_ => (&MOCHA, true),
|
||||
"kicad" => (&KICAD, true),
|
||||
_ => (&KICAD, true),
|
||||
};
|
||||
CURRENT.with(|c| *c.borrow_mut() = pal);
|
||||
IS_DARK.with(|d| *d.borrow_mut() = dark);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
|
||||
use iced_wgpu::core::text::highlighter;
|
||||
|
|
@ -32,6 +33,15 @@ const COR_OPERATOR: u8 = 62;
|
|||
const COR_BRACKET: u8 = 63;
|
||||
const COR_TYPE_ANN: u8 = 64;
|
||||
|
||||
// Per-identifier rainbow. Each user-introduced name (let, fn, params, for var,
|
||||
// math-form fn def) gets one of eight palette slots, picked with a stride
|
||||
// that avoids adjacent colors landing on consecutive identifiers. Subsequent
|
||||
// references resolve to the same slot so the name reads the same color
|
||||
// throughout the document.
|
||||
const USER_IDENT_BASE: u8 = 70;
|
||||
const USER_IDENT_PALETTE_SIZE: u8 = 8;
|
||||
const USER_IDENT_HOP: u32 = 3;
|
||||
|
||||
const MD_HEADING_MARKER: u8 = 26;
|
||||
const MD_H1: u8 = 27;
|
||||
const MD_H2: u8 = 28;
|
||||
|
|
@ -92,6 +102,7 @@ pub struct SyntaxHighlighter {
|
|||
in_fenced_code: bool,
|
||||
current_line: usize,
|
||||
line_decors: Vec<LineDecor>,
|
||||
user_idents: HashMap<String, u8>,
|
||||
}
|
||||
|
||||
impl SyntaxHighlighter {
|
||||
|
|
@ -132,6 +143,87 @@ impl SyntaxHighlighter {
|
|||
|
||||
self.in_fenced_code = false;
|
||||
self.current_line = 0;
|
||||
|
||||
self.scan_user_idents(source);
|
||||
}
|
||||
|
||||
/// Walk the source, find every identifier introduction site (let, fn,
|
||||
/// for, math-form fn def, params), and assign each unique name a slot
|
||||
/// in the user-ident rainbow. Subsequent references in `highlight_cordial`
|
||||
/// look the name up here.
|
||||
fn scan_user_idents(&mut self, source: &str) {
|
||||
self.user_idents.clear();
|
||||
let mut next_slot: u32 = 0;
|
||||
|
||||
for line in source.split('\n') {
|
||||
let trimmed = line.trim_start();
|
||||
let bytes = trimmed.as_bytes();
|
||||
|
||||
// `let IDENT...`
|
||||
if let Some(rest) = trimmed.strip_prefix("let ") {
|
||||
let mut i = 0;
|
||||
let rb = rest.as_bytes();
|
||||
while i < rb.len() && rb[i] == b' ' { i += 1; }
|
||||
let name_start = i;
|
||||
while i < rb.len() && (rb[i].is_ascii_alphanumeric() || rb[i] == b'_') { i += 1; }
|
||||
if i > name_start {
|
||||
assign_user_ident(&mut self.user_idents, &mut next_slot, &rest[name_start..i]);
|
||||
}
|
||||
while i < rb.len() && rb[i] == b' ' { i += 1; }
|
||||
if i < rb.len() && rb[i] == b'(' {
|
||||
extract_paren_idents(&rest[i + 1..], &mut self.user_idents, &mut next_slot);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// `fn IDENT(...)`
|
||||
if let Some(rest) = trimmed.strip_prefix("fn ") {
|
||||
let mut i = 0;
|
||||
let rb = rest.as_bytes();
|
||||
while i < rb.len() && rb[i] == b' ' { i += 1; }
|
||||
let name_start = i;
|
||||
while i < rb.len() && (rb[i].is_ascii_alphanumeric() || rb[i] == b'_') { i += 1; }
|
||||
if i > name_start {
|
||||
assign_user_ident(&mut self.user_idents, &mut next_slot, &rest[name_start..i]);
|
||||
}
|
||||
while i < rb.len() && rb[i] == b' ' { i += 1; }
|
||||
if i < rb.len() && rb[i] == b'(' {
|
||||
extract_paren_idents(&rest[i + 1..], &mut self.user_idents, &mut next_slot);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// `for IDENT in ...`
|
||||
if let Some(rest) = trimmed.strip_prefix("for ") {
|
||||
let rb = rest.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < rb.len() && rb[i] == b' ' { i += 1; }
|
||||
let name_start = i;
|
||||
while i < rb.len() && (rb[i].is_ascii_alphanumeric() || rb[i] == b'_') { i += 1; }
|
||||
if i > name_start {
|
||||
assign_user_ident(&mut self.user_idents, &mut next_slot, &rest[name_start..i]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// `IDENT(...) = ...` math-form fn def, OR `IDENT = ...` assignment
|
||||
let mut i = 0;
|
||||
let name_start = i;
|
||||
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') { i += 1; }
|
||||
if i > name_start {
|
||||
let name = &trimmed[name_start..i];
|
||||
let mut j = i;
|
||||
while j < bytes.len() && bytes[j] == b' ' { j += 1; }
|
||||
if j < bytes.len() {
|
||||
if bytes[j] == b'(' {
|
||||
assign_user_ident(&mut self.user_idents, &mut next_slot, name);
|
||||
extract_paren_idents(&trimmed[j + 1..], &mut self.user_idents, &mut next_slot);
|
||||
} else if bytes[j] == b'=' && (j + 1 >= bytes.len() || bytes[j + 1] != b'=') {
|
||||
assign_user_ident(&mut self.user_idents, &mut next_slot, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_markdown(&self, line: &str) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
||||
|
|
@ -204,7 +296,50 @@ impl SyntaxHighlighter {
|
|||
/// spans. Idempotent, single-pass; each branch either consumes a whole
|
||||
/// token or advances one byte. Unknown bytes get no highlight (they fall
|
||||
/// through to the editor's default text color).
|
||||
fn highlight_cordial(line: &str) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
||||
fn assign_user_ident(map: &mut HashMap<String, u8>, slot: &mut u32, name: &str) {
|
||||
if name.is_empty()
|
||||
|| is_cordial_keyword(name)
|
||||
|| is_cordial_builtin(name)
|
||||
|| is_cordial_type_annotation(name)
|
||||
|| name == "pi"
|
||||
|| name == "where"
|
||||
|| name == "from"
|
||||
|| name == "solve"
|
||||
|| map.contains_key(name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let color_idx = ((*slot * USER_IDENT_HOP) % USER_IDENT_PALETTE_SIZE as u32) as u8;
|
||||
map.insert(name.to_string(), color_idx);
|
||||
*slot += 1;
|
||||
}
|
||||
|
||||
fn extract_paren_idents(s: &str, map: &mut HashMap<String, u8>, slot: &mut u32) {
|
||||
let bytes = s.as_bytes();
|
||||
let mut i = 0;
|
||||
let mut depth: i32 = 1;
|
||||
while i < bytes.len() && depth > 0 {
|
||||
match bytes[i] {
|
||||
b'(' => { depth += 1; i += 1; }
|
||||
b')' => { depth -= 1; i += 1; }
|
||||
b':' => {
|
||||
// Skip the type identifier that follows; type names belong
|
||||
// to the type-annotation color, not the user-ident rainbow.
|
||||
i += 1;
|
||||
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') { i += 1; }
|
||||
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') { i += 1; }
|
||||
}
|
||||
c if c.is_ascii_alphabetic() || c == b'_' => {
|
||||
let start = i;
|
||||
while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') { i += 1; }
|
||||
assign_user_ident(map, slot, &s[start..i]);
|
||||
}
|
||||
_ => i += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_cordial(line: &str, user_idents: &HashMap<String, u8>) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
||||
let bytes = line.as_bytes();
|
||||
let len = bytes.len();
|
||||
let mut spans: Vec<(Range<usize>, SyntaxHighlight)> = Vec::new();
|
||||
|
|
@ -317,9 +452,7 @@ fn highlight_cordial(line: &str) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Identifier → keyword / builtin / plain. Plain idents get no
|
||||
// highlight so user-defined names stay in the default editor
|
||||
// color — keeps the document from looking like confetti.
|
||||
// Identifier → keyword / builtin / type-annotation / user-rainbow.
|
||||
if is_ident_byte(c) && !c.is_ascii_digit() {
|
||||
let start = i;
|
||||
while i < len && is_ident_byte(bytes[i]) { i += 1; }
|
||||
|
|
@ -329,9 +462,9 @@ fn highlight_cordial(line: &str) -> Vec<(Range<usize>, SyntaxHighlight)> {
|
|||
} else if is_cordial_builtin(word) {
|
||||
spans.push((start..i, SyntaxHighlight { kind: COR_BUILTIN_FN }));
|
||||
} else if is_cordial_type_annotation(word) && last_token_is_colon(&spans) {
|
||||
// Type annotation immediately after `:` in a `let x: T = …`
|
||||
// reads the type name; give it the yellow/type color.
|
||||
spans.push((start..i, SyntaxHighlight { kind: COR_TYPE_ANN }));
|
||||
} else if let Some(&slot) = user_idents.get(word) {
|
||||
spans.push((start..i, SyntaxHighlight { kind: USER_IDENT_BASE + slot }));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -682,6 +815,7 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
in_fenced_code: false,
|
||||
current_line: 0,
|
||||
line_decors: Vec::new(),
|
||||
user_idents: HashMap::new(),
|
||||
};
|
||||
h.rebuild(&settings.source);
|
||||
h
|
||||
|
|
@ -740,7 +874,7 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
if ln < self.line_kinds.len()
|
||||
&& matches!(self.line_kinds[ln], LineKind::Cordial | LineKind::Eval | LineKind::Comment)
|
||||
{
|
||||
return highlight_cordial(line).into_iter();
|
||||
return highlight_cordial(line, &self.user_idents).into_iter();
|
||||
}
|
||||
|
||||
if ln >= self.line_offsets.len() {
|
||||
|
|
@ -775,6 +909,19 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
|
||||
pub fn highlight_color(kind: u8) -> Color {
|
||||
let p = palette::current();
|
||||
if kind >= USER_IDENT_BASE && kind < USER_IDENT_BASE + USER_IDENT_PALETTE_SIZE {
|
||||
return match kind - USER_IDENT_BASE {
|
||||
0 => p.red,
|
||||
1 => p.green,
|
||||
2 => p.peach,
|
||||
3 => p.blue,
|
||||
4 => p.mauve,
|
||||
5 => p.teal,
|
||||
6 => p.yellow,
|
||||
7 => p.pink,
|
||||
_ => p.text,
|
||||
};
|
||||
}
|
||||
match kind {
|
||||
0 => p.mauve,
|
||||
1 => p.blue,
|
||||
|
|
|
|||
Loading…
Reference in New Issue