feat: syntax highlighting for diff view

This commit is contained in:
2026-05-25 00:08:22 +01:00
parent b3e041a257
commit a6cf96ea96
20 changed files with 1295 additions and 722 deletions

View File

@@ -1,6 +1,9 @@
use crate::colors::{hex, hex_alpha};
use super::{Theme, ThemeColors, ThemeMode};
use super::{
Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight,
syntax::{build, key},
};
pub(crate) const FAMILY_ID: &str = "catppuccin";
pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
@@ -8,7 +11,31 @@ pub(crate) const FAMILY_LABEL: &str = "Catppuccin";
pub(crate) const LATTE_LABEL: &str = "Catppuccin Latte";
pub(crate) const MOCHA_LABEL: &str = "Catppuccin Mocha";
pub(crate) const fn latte() -> Theme {
const fn highlight(color: u32) -> ThemeSyntaxHighlight {
ThemeSyntaxHighlight {
color: hex(color),
font_style: gpui::FontStyle::Normal,
font_weight: None,
}
}
const fn highlight_italic(color: u32) -> ThemeSyntaxHighlight {
ThemeSyntaxHighlight {
color: hex(color),
font_style: gpui::FontStyle::Italic,
font_weight: None,
}
}
const fn highlight_weight(color: u32, font_weight: gpui::FontWeight) -> ThemeSyntaxHighlight {
ThemeSyntaxHighlight {
color: hex(color),
font_style: gpui::FontStyle::Normal,
font_weight: Some(font_weight),
}
}
pub(crate) fn latte() -> Theme {
Theme {
id: "catppuccin-latte",
name: LATTE_LABEL,
@@ -61,10 +88,63 @@ pub(crate) const fn latte() -> Theme {
info_solid: hex(0x1e66f5),
info_on_solid: hex(0xeff1f5),
},
syntax: build([
(key::ATTRIBUTE, highlight(0x179299)),
(key::BOOLEAN, highlight(0xfe640b)),
(key::COMMENT, highlight(0x7c7f93)),
(key::COMMENT_DOC, highlight(0x8c8fa1)),
(key::CONSTANT, highlight(0xfe640b)),
(key::CONSTRUCTOR, highlight(0x209fb5)),
(key::EMBEDDED, highlight(0x4c4f69)),
(key::EMPHASIS, highlight(0x1e66f5)),
(
key::EMPHASIS_STRONG,
highlight_weight(0xdf8e1d, gpui::FontWeight::BOLD),
),
(key::ENUM, highlight(0x179299)),
(key::FUNCTION, highlight(0x1e66f5)),
(key::HINT, highlight(0x9ca0b0)),
(key::KEYWORD, highlight(0x8839ef)),
(key::LABEL, highlight(0x7287fd)),
(key::LINK_TEXT, highlight_italic(0x1e66f5)),
(key::LINK_URI, highlight(0x179299)),
(key::NAMESPACE, highlight(0x4c4f69)),
(key::NUMBER, highlight(0xfe640b)),
(key::OPERATOR, highlight(0x04a5e5)),
(key::PREDICTIVE, highlight_italic(0x9ca0b0)),
(key::PREPROC, highlight(0x8839ef)),
(key::PRIMARY, highlight(0x4c4f69)),
(key::PROPERTY, highlight(0xdc8a78)),
(key::PUNCTUATION, highlight(0x6c6f85)),
(key::PUNCTUATION_BRACKET, highlight(0x7c7f93)),
(key::PUNCTUATION_DELIMITER, highlight(0x7c7f93)),
(key::PUNCTUATION_LIST_MARKER, highlight(0xd20f39)),
(key::PUNCTUATION_MARKUP, highlight(0xd20f39)),
(key::PUNCTUATION_SPECIAL, highlight(0xe64553)),
(key::SELECTOR, highlight(0x40a02b)),
(key::SELECTOR_PSEUDO, highlight(0x1e66f5)),
(key::STRING, highlight(0x40a02b)),
(key::STRING_ESCAPE, highlight(0xea76cb)),
(key::STRING_REGEX, highlight(0xfe640b)),
(key::STRING_SPECIAL, highlight(0xfe640b)),
(key::STRING_SPECIAL_SYMBOL, highlight(0xfe640b)),
(key::TAG, highlight(0x1e66f5)),
(key::TEXT_LITERAL, highlight(0x40a02b)),
(
key::TITLE,
highlight_weight(0xdc8a78, gpui::FontWeight::NORMAL),
),
(key::TYPE, highlight(0xdf8e1d)),
(key::VARIABLE, highlight(0x4c4f69)),
(key::VARIABLE_SPECIAL, highlight(0xfe640b)),
(key::VARIANT, highlight(0x7287fd)),
(key::DIFF_PLUS, highlight(0x40a02b)),
(key::DIFF_MINUS, highlight(0xd20f39)),
]),
}
}
pub(crate) const fn mocha() -> Theme {
pub(crate) fn mocha() -> Theme {
Theme {
id: "catppuccin-mocha",
name: MOCHA_LABEL,
@@ -117,5 +197,58 @@ pub(crate) const fn mocha() -> Theme {
info_solid: hex(0x89b4fa),
info_on_solid: hex(0x1e1e2e),
},
syntax: build([
(key::ATTRIBUTE, highlight(0x94e2d5)),
(key::BOOLEAN, highlight(0xfab387)),
(key::COMMENT, highlight(0x6c7086)),
(key::COMMENT_DOC, highlight(0x7f849c)),
(key::CONSTANT, highlight(0xfab387)),
(key::CONSTRUCTOR, highlight(0x74c7ec)),
(key::EMBEDDED, highlight(0xcdd6f4)),
(key::EMPHASIS, highlight(0x89b4fa)),
(
key::EMPHASIS_STRONG,
highlight_weight(0xf9e2af, gpui::FontWeight::BOLD),
),
(key::ENUM, highlight(0x94e2d5)),
(key::FUNCTION, highlight(0x89b4fa)),
(key::HINT, highlight(0x9399b2)),
(key::KEYWORD, highlight(0xcba6f7)),
(key::LABEL, highlight(0xb4befe)),
(key::LINK_TEXT, highlight_italic(0x89b4fa)),
(key::LINK_URI, highlight(0x94e2d5)),
(key::NAMESPACE, highlight(0xcdd6f4)),
(key::NUMBER, highlight(0xfab387)),
(key::OPERATOR, highlight(0x89dceb)),
(key::PREDICTIVE, highlight_italic(0x9399b2)),
(key::PREPROC, highlight(0xcba6f7)),
(key::PRIMARY, highlight(0xcdd6f4)),
(key::PROPERTY, highlight(0xf5e0dc)),
(key::PUNCTUATION, highlight(0xa6adc8)),
(key::PUNCTUATION_BRACKET, highlight(0x9399b2)),
(key::PUNCTUATION_DELIMITER, highlight(0x9399b2)),
(key::PUNCTUATION_LIST_MARKER, highlight(0xf38ba8)),
(key::PUNCTUATION_MARKUP, highlight(0xf38ba8)),
(key::PUNCTUATION_SPECIAL, highlight(0xeba0ac)),
(key::SELECTOR, highlight(0xa6e3a1)),
(key::SELECTOR_PSEUDO, highlight(0x89b4fa)),
(key::STRING, highlight(0xa6e3a1)),
(key::STRING_ESCAPE, highlight(0xf5c2e7)),
(key::STRING_REGEX, highlight(0xfab387)),
(key::STRING_SPECIAL, highlight(0xfab387)),
(key::STRING_SPECIAL_SYMBOL, highlight(0xfab387)),
(key::TAG, highlight(0x89b4fa)),
(key::TEXT_LITERAL, highlight(0xa6e3a1)),
(
key::TITLE,
highlight_weight(0xf5e0dc, gpui::FontWeight::NORMAL),
),
(key::TYPE, highlight(0xf9e2af)),
(key::VARIABLE, highlight(0xcdd6f4)),
(key::VARIABLE_SPECIAL, highlight(0xfab387)),
(key::VARIANT, highlight(0xb4befe)),
(key::DIFF_PLUS, highlight(0xa6e3a1)),
(key::DIFF_MINUS, highlight(0xf38ba8)),
]),
}
}

165
src/theme/syntax.rs Normal file
View File

@@ -0,0 +1,165 @@
use std::collections::BTreeMap;
use gpui::Rgba;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeSyntaxHighlight {
pub color: Rgba,
pub font_style: gpui::FontStyle,
pub font_weight: Option<gpui::FontWeight>,
}
pub const HIGHLIGHT_NAME_COUNT: usize = HIGHLIGHT_NAMES.len();
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ThemeSyntax([ThemeSyntaxHighlight; HIGHLIGHT_NAME_COUNT]);
impl ThemeSyntax {
pub fn resolve(&self, key: &str) -> ThemeSyntaxHighlight {
Self::index_of(key)
.and_then(|index| self.get(index))
.unwrap_or_else(|| self.text_literal())
}
pub fn get(&self, index: usize) -> Option<ThemeSyntaxHighlight> {
self.0.get(index).copied()
}
pub fn as_slice(&self) -> &[ThemeSyntaxHighlight] {
&self.0
}
fn text_literal(&self) -> ThemeSyntaxHighlight {
self.get(Self::index_of(key::TEXT_LITERAL).expect("missing text.literal index"))
.expect("theme syntax must define text.literal")
}
fn index_of(key: &str) -> Option<usize> {
HIGHLIGHT_NAMES
.iter()
.position(|candidate| *candidate == key)
}
}
pub const HIGHLIGHT_NAMES: &[&str] = &[
key::ATTRIBUTE,
key::BOOLEAN,
key::COMMENT,
key::COMMENT_DOC,
key::CONSTANT,
key::CONSTRUCTOR,
key::DIFF_MINUS,
key::DIFF_PLUS,
key::EMBEDDED,
key::EMPHASIS,
key::EMPHASIS_STRONG,
key::ENUM,
key::FUNCTION,
key::HINT,
key::KEYWORD,
key::LABEL,
key::LINK_TEXT,
key::LINK_URI,
key::NAMESPACE,
key::NUMBER,
key::OPERATOR,
key::PREDICTIVE,
key::PREPROC,
key::PRIMARY,
key::PROPERTY,
key::PUNCTUATION,
key::PUNCTUATION_BRACKET,
key::PUNCTUATION_DELIMITER,
key::PUNCTUATION_LIST_MARKER,
key::PUNCTUATION_MARKUP,
key::PUNCTUATION_SPECIAL,
key::SELECTOR,
key::SELECTOR_PSEUDO,
key::STRING,
key::STRING_ESCAPE,
key::STRING_REGEX,
key::STRING_SPECIAL,
key::STRING_SPECIAL_SYMBOL,
key::TAG,
key::TEXT_LITERAL,
key::TITLE,
key::TYPE,
key::VARIABLE,
key::VARIABLE_SPECIAL,
key::VARIANT,
];
pub(crate) fn build(
entries: impl IntoIterator<Item = (&'static str, ThemeSyntaxHighlight)>,
) -> ThemeSyntax {
let syntax_by_name: BTreeMap<&'static str, ThemeSyntaxHighlight> = entries
.into_iter()
.map(|(name, style)| (name, style))
.collect();
debug_assert!(syntax_by_name.contains_key(key::TEXT_LITERAL));
debug_assert!(
syntax_by_name
.keys()
.all(|name| HIGHLIGHT_NAMES.contains(name))
);
let fallback = *syntax_by_name
.get(key::TEXT_LITERAL)
.expect("theme syntax must define text.literal");
ThemeSyntax(std::array::from_fn(|index| {
syntax_by_name
.get(HIGHLIGHT_NAMES[index])
.copied()
.unwrap_or(fallback)
}))
}
pub(crate) mod key {
pub const ATTRIBUTE: &str = "attribute";
pub const BOOLEAN: &str = "boolean";
pub const COMMENT: &str = "comment";
pub const COMMENT_DOC: &str = "comment.doc";
pub const CONSTANT: &str = "constant";
pub const CONSTRUCTOR: &str = "constructor";
pub const DIFF_MINUS: &str = "diff.minus";
pub const DIFF_PLUS: &str = "diff.plus";
pub const EMBEDDED: &str = "embedded";
pub const EMPHASIS: &str = "emphasis";
pub const EMPHASIS_STRONG: &str = "emphasis.strong";
pub const ENUM: &str = "enum";
pub const FUNCTION: &str = "function";
pub const HINT: &str = "hint";
pub const KEYWORD: &str = "keyword";
pub const LABEL: &str = "label";
pub const LINK_TEXT: &str = "link_text";
pub const LINK_URI: &str = "link_uri";
pub const NAMESPACE: &str = "namespace";
pub const NUMBER: &str = "number";
pub const OPERATOR: &str = "operator";
pub const PREDICTIVE: &str = "predictive";
pub const PREPROC: &str = "preproc";
pub const PRIMARY: &str = "primary";
pub const PROPERTY: &str = "property";
pub const PUNCTUATION: &str = "punctuation";
pub const PUNCTUATION_BRACKET: &str = "punctuation.bracket";
pub const PUNCTUATION_DELIMITER: &str = "punctuation.delimiter";
pub const PUNCTUATION_LIST_MARKER: &str = "punctuation.list_marker";
pub const PUNCTUATION_MARKUP: &str = "punctuation.markup";
pub const PUNCTUATION_SPECIAL: &str = "punctuation.special";
pub const SELECTOR: &str = "selector";
pub const SELECTOR_PSEUDO: &str = "selector.pseudo";
pub const STRING: &str = "string";
pub const STRING_ESCAPE: &str = "string.escape";
pub const STRING_REGEX: &str = "string.regex";
pub const STRING_SPECIAL: &str = "string.special";
pub const STRING_SPECIAL_SYMBOL: &str = "string.special.symbol";
pub const TAG: &str = "tag";
pub const TEXT_LITERAL: &str = "text.literal";
pub const TITLE: &str = "title";
pub const TYPE: &str = "type";
pub const VARIABLE: &str = "variable";
pub const VARIABLE_SPECIAL: &str = "variable.special";
pub const VARIANT: &str = "variant";
}