logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
26
crates/theme_importer/Cargo.toml
Normal file
26
crates/theme_importer/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "theme_importer"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
indexmap.workspace = true
|
||||
log.workspace = true
|
||||
palette.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
simplelog.workspace= true
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
theme.workspace = true
|
||||
theme_settings.workspace = true
|
||||
vscode_theme = "0.2.0"
|
||||
1
crates/theme_importer/LICENSE-GPL
Symbolic link
1
crates/theme_importer/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
5
crates/theme_importer/README.md
Normal file
5
crates/theme_importer/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Zed Theme Importer
|
||||
|
||||
```sh
|
||||
cargo run -p theme_importer -- dark-plus-syntax-color-theme.json --output output-theme.json
|
||||
```
|
||||
56
crates/theme_importer/src/color.rs
Normal file
56
crates/theme_importer/src/color.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use anyhow::Result;
|
||||
use gpui::Hsla;
|
||||
use palette::FromColor;
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn try_parse_color(color: &str) -> Result<Hsla> {
|
||||
let rgba = gpui::Rgba::try_from(color)?;
|
||||
let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
|
||||
let hsla = palette::Hsla::from_color(rgba);
|
||||
|
||||
let hsla = gpui::hsla(
|
||||
hsla.hue.into_positive_degrees() / 360.,
|
||||
hsla.saturation,
|
||||
hsla.lightness,
|
||||
hsla.alpha,
|
||||
);
|
||||
|
||||
Ok(hsla)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn pack_color(color: Hsla) -> u32 {
|
||||
let hsla = palette::Hsla::from_components((color.h * 360., color.s, color.l, color.a));
|
||||
let rgba = palette::rgb::Srgba::from_color(hsla);
|
||||
let rgba = rgba.into_format::<u8, u8>();
|
||||
|
||||
u32::from(rgba)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_serialize_color() {
|
||||
let color = "#b4637aff";
|
||||
let hsla = try_parse_color(color).unwrap();
|
||||
let packed = pack_color(hsla);
|
||||
|
||||
assert_eq!(format!("#{:x}", packed), color);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_serialize_color_with_palette() {
|
||||
let color = "#b4637aff";
|
||||
|
||||
let rgba = gpui::Rgba::try_from(color).unwrap();
|
||||
let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
|
||||
let hsla = palette::Hsla::from_color(rgba);
|
||||
|
||||
let rgba = palette::rgb::Srgba::from_color(hsla);
|
||||
let rgba = rgba.into_format::<u8, u8>();
|
||||
|
||||
assert_eq!(format!("#{:x}", rgba), color);
|
||||
}
|
||||
}
|
||||
130
crates/theme_importer/src/main.rs
Normal file
130
crates/theme_importer/src/main.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
mod color;
|
||||
mod vscode;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Parser;
|
||||
use collections::IndexMap;
|
||||
use log::LevelFilter;
|
||||
use serde::Deserialize;
|
||||
use simplelog::ColorChoice;
|
||||
use simplelog::{TermLogger, TerminalMode};
|
||||
use theme::{Appearance, AppearanceContent};
|
||||
|
||||
use crate::vscode::VsCodeTheme;
|
||||
use crate::vscode::VsCodeThemeConverter;
|
||||
|
||||
const ZED_THEME_SCHEMA_URL: &str = "https://zed.dev/schema/themes/v0.2.0.json";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThemeAppearanceJson {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl From<ThemeAppearanceJson> for AppearanceContent {
|
||||
fn from(value: ThemeAppearanceJson) -> Self {
|
||||
match value {
|
||||
ThemeAppearanceJson::Light => Self::Light,
|
||||
ThemeAppearanceJson::Dark => Self::Dark,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThemeAppearanceJson> for Appearance {
|
||||
fn from(value: ThemeAppearanceJson) -> Self {
|
||||
match value {
|
||||
ThemeAppearanceJson::Light => Self::Light,
|
||||
ThemeAppearanceJson::Dark => Self::Dark,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ThemeMetadata {
|
||||
pub name: String,
|
||||
pub file_name: String,
|
||||
pub appearance: ThemeAppearanceJson,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// The path to the theme to import.
|
||||
theme_path: PathBuf,
|
||||
|
||||
/// Whether to warn when values are missing from the theme.
|
||||
#[arg(long)]
|
||||
warn_on_missing: bool,
|
||||
|
||||
/// The path to write the output to.
|
||||
#[arg(long, short)]
|
||||
output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let log_config = {
|
||||
let mut config = simplelog::ConfigBuilder::new();
|
||||
|
||||
if !args.warn_on_missing {
|
||||
config.add_filter_ignore_str("theme_printer");
|
||||
}
|
||||
|
||||
config.build()
|
||||
};
|
||||
|
||||
TermLogger::init(
|
||||
LevelFilter::Trace,
|
||||
log_config,
|
||||
TerminalMode::Stderr,
|
||||
ColorChoice::Auto,
|
||||
)
|
||||
.expect("could not initialize logger");
|
||||
|
||||
let theme_file_path = args.theme_path;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
match File::open(&theme_file_path).and_then(|mut file| file.read_to_end(&mut buffer)) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
log::info!("Failed to open file at path: {:?}", theme_file_path);
|
||||
return Err(err)?;
|
||||
}
|
||||
};
|
||||
|
||||
let vscode_theme: VsCodeTheme = serde_json_lenient::from_slice(&buffer)
|
||||
.context(format!("failed to parse theme {theme_file_path:?}"))?;
|
||||
|
||||
let theme_metadata = ThemeMetadata {
|
||||
name: vscode_theme.name.clone().unwrap_or("".to_string()),
|
||||
appearance: ThemeAppearanceJson::Dark,
|
||||
file_name: "".to_string(),
|
||||
};
|
||||
|
||||
let converter = VsCodeThemeConverter::new(vscode_theme, theme_metadata, IndexMap::default());
|
||||
|
||||
let theme = converter.convert()?;
|
||||
let mut theme = serde_json::to_value(theme).unwrap();
|
||||
theme.as_object_mut().unwrap().insert(
|
||||
"$schema".to_string(),
|
||||
serde_json::Value::String(ZED_THEME_SCHEMA_URL.to_string()),
|
||||
);
|
||||
let theme_json = serde_json::to_string_pretty(&theme).unwrap();
|
||||
|
||||
if let Some(output) = args.output {
|
||||
let mut file = File::create(output)?;
|
||||
file.write_all(theme_json.as_bytes())?;
|
||||
} else {
|
||||
println!("{}", theme_json);
|
||||
}
|
||||
|
||||
log::info!("Done!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
7
crates/theme_importer/src/vscode.rs
Normal file
7
crates/theme_importer/src/vscode.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
mod converter;
|
||||
mod syntax;
|
||||
mod theme;
|
||||
|
||||
pub use converter::*;
|
||||
pub use syntax::*;
|
||||
pub use theme::*;
|
||||
277
crates/theme_importer/src/vscode/converter.rs
Normal file
277
crates/theme_importer/src/vscode/converter.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use anyhow::Result;
|
||||
use collections::IndexMap;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme_settings::{
|
||||
FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
|
||||
ThemeColorsContent, ThemeContent, ThemeStyleContent, WindowBackgroundContent,
|
||||
};
|
||||
|
||||
use crate::ThemeMetadata;
|
||||
use crate::vscode::{VsCodeTheme, VsCodeTokenScope};
|
||||
|
||||
use super::ZedSyntaxToken;
|
||||
|
||||
pub(crate) fn try_parse_font_weight(font_style: &str) -> Option<FontWeightContent> {
|
||||
match font_style {
|
||||
style if style.contains("bold") => Some(FontWeightContent::BOLD),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_parse_font_style(font_style: &str) -> Option<FontStyleContent> {
|
||||
match font_style {
|
||||
style if style.contains("italic") => Some(FontStyleContent::Italic),
|
||||
style if style.contains("oblique") => Some(FontStyleContent::Oblique),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VsCodeThemeConverter {
|
||||
theme: VsCodeTheme,
|
||||
theme_metadata: ThemeMetadata,
|
||||
syntax_overrides: IndexMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
impl VsCodeThemeConverter {
|
||||
pub fn new(
|
||||
theme: VsCodeTheme,
|
||||
theme_metadata: ThemeMetadata,
|
||||
syntax_overrides: IndexMap<String, Vec<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
theme,
|
||||
theme_metadata,
|
||||
syntax_overrides,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert(self) -> Result<ThemeContent> {
|
||||
let appearance = self.theme_metadata.appearance.into();
|
||||
|
||||
let status_colors = self.convert_status_colors()?;
|
||||
let theme_colors = self.convert_theme_colors()?;
|
||||
let syntax_theme = self.convert_syntax_theme()?;
|
||||
|
||||
Ok(ThemeContent {
|
||||
name: self.theme_metadata.name,
|
||||
appearance,
|
||||
style: ThemeStyleContent {
|
||||
window_background_appearance: Some(WindowBackgroundContent::Opaque),
|
||||
accents: Vec::new(), //TODO can we read this from the theme?
|
||||
colors: theme_colors,
|
||||
status: status_colors,
|
||||
players: Vec::new(),
|
||||
syntax: syntax_theme,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_status_colors(&self) -> Result<StatusColorsContent> {
|
||||
let vscode_colors = &self.theme.colors;
|
||||
|
||||
let vscode_base_status_colors = StatusColorsContent {
|
||||
hint: Some("#969696ff".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(StatusColorsContent {
|
||||
conflict: vscode_colors
|
||||
.git_decoration
|
||||
.conflicting_resource_foreground
|
||||
.clone(),
|
||||
created: vscode_colors.editor_gutter.added_background.clone(),
|
||||
deleted: vscode_colors.editor_gutter.deleted_background.clone(),
|
||||
error: vscode_colors.editor_error.foreground.clone(),
|
||||
error_background: vscode_colors.editor_error.background.clone(),
|
||||
error_border: vscode_colors.editor_error.border.clone(),
|
||||
hidden: vscode_colors.tab.inactive_foreground.clone(),
|
||||
hint: vscode_colors
|
||||
.editor_inlay_hint
|
||||
.foreground
|
||||
.clone()
|
||||
.or(vscode_base_status_colors.hint),
|
||||
hint_border: vscode_colors.editor_hint.border.clone(),
|
||||
ignored: vscode_colors
|
||||
.git_decoration
|
||||
.ignored_resource_foreground
|
||||
.clone(),
|
||||
info: vscode_colors.editor_info.foreground.clone(),
|
||||
info_background: vscode_colors.editor_info.background.clone(),
|
||||
info_border: vscode_colors.editor_info.border.clone(),
|
||||
modified: vscode_colors.editor_gutter.modified_background.clone(),
|
||||
// renamed: None,
|
||||
// success: None,
|
||||
warning: vscode_colors.editor_warning.foreground.clone(),
|
||||
warning_background: vscode_colors.editor_warning.background.clone(),
|
||||
warning_border: vscode_colors.editor_warning.border.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_theme_colors(&self) -> Result<ThemeColorsContent> {
|
||||
let vscode_colors = &self.theme.colors;
|
||||
|
||||
let vscode_panel_border = vscode_colors.panel.border.clone();
|
||||
let vscode_tab_inactive_background = vscode_colors.tab.inactive_background.clone();
|
||||
let vscode_editor_foreground = vscode_colors.editor.foreground.clone();
|
||||
let vscode_editor_background = vscode_colors.editor.background.clone();
|
||||
let vscode_scrollbar_slider_background = vscode_colors.scrollbar_slider.background.clone();
|
||||
let vscode_token_colors_foreground = self
|
||||
.theme
|
||||
.token_colors
|
||||
.iter()
|
||||
.find(|token_color| token_color.scope.is_none())
|
||||
.and_then(|token_color| token_color.settings.foreground.as_ref())
|
||||
.cloned();
|
||||
|
||||
Ok(ThemeColorsContent {
|
||||
border: vscode_panel_border.clone(),
|
||||
border_variant: vscode_panel_border.clone(),
|
||||
border_focused: vscode_colors.focus_border.clone(),
|
||||
border_selected: vscode_panel_border.clone(),
|
||||
border_transparent: vscode_panel_border.clone(),
|
||||
border_disabled: vscode_panel_border.clone(),
|
||||
elevated_surface_background: vscode_colors.dropdown.background.clone(),
|
||||
surface_background: vscode_colors.panel.background.clone(),
|
||||
background: vscode_editor_background.clone(),
|
||||
element_background: vscode_colors.button.background.clone(),
|
||||
element_hover: vscode_colors.list.hover_background.clone(),
|
||||
element_selected: vscode_colors.list.active_selection_background.clone(),
|
||||
drop_target_background: vscode_colors.list.drop_background.clone(),
|
||||
ghost_element_hover: vscode_colors.list.hover_background.clone(),
|
||||
ghost_element_selected: vscode_colors.list.active_selection_background.clone(),
|
||||
text: vscode_colors
|
||||
.foreground
|
||||
.clone()
|
||||
.or(vscode_token_colors_foreground.clone()),
|
||||
text_muted: vscode_colors.tab.inactive_foreground.clone(),
|
||||
status_bar_background: vscode_colors.status_bar.background.clone(),
|
||||
title_bar_background: vscode_colors.title_bar.active_background.clone(),
|
||||
toolbar_background: vscode_colors
|
||||
.breadcrumb
|
||||
.background
|
||||
.clone()
|
||||
.or(vscode_editor_background.clone()),
|
||||
tab_bar_background: vscode_colors.editor_group_header.tabs_background.clone(),
|
||||
tab_inactive_background: vscode_tab_inactive_background.clone(),
|
||||
tab_active_background: vscode_colors
|
||||
.tab
|
||||
.active_background
|
||||
.clone()
|
||||
.or(vscode_tab_inactive_background),
|
||||
search_match_background: vscode_colors.editor.find_match_background.clone(),
|
||||
panel_background: vscode_colors.panel.background.clone(),
|
||||
pane_group_border: vscode_colors.editor_group.border.clone(),
|
||||
scrollbar_thumb_background: vscode_scrollbar_slider_background.clone(),
|
||||
scrollbar_thumb_hover_background: vscode_colors
|
||||
.scrollbar_slider
|
||||
.hover_background
|
||||
.clone(),
|
||||
scrollbar_thumb_active_background: vscode_colors
|
||||
.scrollbar_slider
|
||||
.active_background
|
||||
.clone(),
|
||||
scrollbar_thumb_border: vscode_scrollbar_slider_background,
|
||||
scrollbar_track_background: vscode_editor_background.clone(),
|
||||
scrollbar_track_border: vscode_colors.editor_overview_ruler.border.clone(),
|
||||
minimap_thumb_background: vscode_colors.minimap_slider.background.clone(),
|
||||
minimap_thumb_hover_background: vscode_colors.minimap_slider.hover_background.clone(),
|
||||
minimap_thumb_active_background: vscode_colors.minimap_slider.active_background.clone(),
|
||||
editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground),
|
||||
editor_background: vscode_editor_background.clone(),
|
||||
editor_gutter_background: vscode_editor_background,
|
||||
editor_active_line_background: vscode_colors.editor.line_highlight_background.clone(),
|
||||
editor_line_number: vscode_colors.editor_line_number.foreground.clone(),
|
||||
editor_active_line_number: vscode_colors.editor.foreground.clone(),
|
||||
editor_wrap_guide: vscode_panel_border.clone(),
|
||||
editor_active_wrap_guide: vscode_panel_border,
|
||||
editor_document_highlight_bracket_background: vscode_colors
|
||||
.editor_bracket_match
|
||||
.background
|
||||
.clone(),
|
||||
terminal_background: vscode_colors.terminal.background.clone(),
|
||||
terminal_ansi_black: vscode_colors.terminal.ansi_black.clone(),
|
||||
terminal_ansi_bright_black: vscode_colors.terminal.ansi_bright_black.clone(),
|
||||
terminal_ansi_red: vscode_colors.terminal.ansi_red.clone(),
|
||||
terminal_ansi_bright_red: vscode_colors.terminal.ansi_bright_red.clone(),
|
||||
terminal_ansi_green: vscode_colors.terminal.ansi_green.clone(),
|
||||
terminal_ansi_bright_green: vscode_colors.terminal.ansi_bright_green.clone(),
|
||||
terminal_ansi_yellow: vscode_colors.terminal.ansi_yellow.clone(),
|
||||
terminal_ansi_bright_yellow: vscode_colors.terminal.ansi_bright_yellow.clone(),
|
||||
terminal_ansi_blue: vscode_colors.terminal.ansi_blue.clone(),
|
||||
terminal_ansi_bright_blue: vscode_colors.terminal.ansi_bright_blue.clone(),
|
||||
terminal_ansi_magenta: vscode_colors.terminal.ansi_magenta.clone(),
|
||||
terminal_ansi_bright_magenta: vscode_colors.terminal.ansi_bright_magenta.clone(),
|
||||
terminal_ansi_cyan: vscode_colors.terminal.ansi_cyan.clone(),
|
||||
terminal_ansi_bright_cyan: vscode_colors.terminal.ansi_bright_cyan.clone(),
|
||||
terminal_ansi_white: vscode_colors.terminal.ansi_white.clone(),
|
||||
terminal_ansi_bright_white: vscode_colors.terminal.ansi_bright_white.clone(),
|
||||
link_text_hover: vscode_colors.text_link.active_foreground.clone(),
|
||||
vim_yank_background: vscode_colors.editor.range_highlight_background.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_syntax_theme(&self) -> Result<IndexMap<String, HighlightStyleContent>> {
|
||||
let mut highlight_styles = IndexMap::default();
|
||||
|
||||
for syntax_token in ZedSyntaxToken::iter() {
|
||||
let override_match = self
|
||||
.syntax_overrides
|
||||
.get(&syntax_token.to_string())
|
||||
.and_then(|scope| {
|
||||
self.theme.token_colors.iter().find(|token_color| {
|
||||
token_color.scope == Some(VsCodeTokenScope::Many(scope.clone()))
|
||||
})
|
||||
});
|
||||
|
||||
let best_match = override_match
|
||||
.or_else(|| syntax_token.find_best_token_color_match(&self.theme.token_colors))
|
||||
.or_else(|| {
|
||||
syntax_token.fallbacks().iter().find_map(|fallback| {
|
||||
fallback.find_best_token_color_match(&self.theme.token_colors)
|
||||
})
|
||||
});
|
||||
|
||||
let Some(token_color) = best_match else {
|
||||
log::warn!("No matching token color found for '{syntax_token}'");
|
||||
continue;
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"Matched '{syntax_token}' to '{}'",
|
||||
token_color
|
||||
.name
|
||||
.clone()
|
||||
.or_else(|| token_color
|
||||
.scope
|
||||
.as_ref()
|
||||
.map(|scope| format!("{:?}", scope)))
|
||||
.unwrap_or_else(|| "no identifier".to_string())
|
||||
);
|
||||
|
||||
let highlight_style = HighlightStyleContent {
|
||||
color: token_color.settings.foreground.clone(),
|
||||
background_color: token_color.settings.background.clone(),
|
||||
font_style: token_color
|
||||
.settings
|
||||
.font_style
|
||||
.as_ref()
|
||||
.and_then(|style| try_parse_font_style(style)),
|
||||
font_weight: token_color
|
||||
.settings
|
||||
.font_style
|
||||
.as_ref()
|
||||
.and_then(|style| try_parse_font_weight(style)),
|
||||
};
|
||||
|
||||
if highlight_style.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
highlight_styles.insert(syntax_token.to_string(), highlight_style);
|
||||
}
|
||||
|
||||
Ok(highlight_styles)
|
||||
}
|
||||
}
|
||||
310
crates/theme_importer/src/vscode/syntax.rs
Normal file
310
crates/theme_importer/src/vscode/syntax.rs
Normal file
@@ -0,0 +1,310 @@
|
||||
use indexmap::IndexMap;
|
||||
use serde::Deserialize;
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum VsCodeTokenScope {
|
||||
One(String),
|
||||
Many(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VsCodeTokenColor {
|
||||
pub name: Option<String>,
|
||||
pub scope: Option<VsCodeTokenScope>,
|
||||
pub settings: VsCodeTokenColorSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VsCodeTokenColorSettings {
|
||||
pub foreground: Option<String>,
|
||||
pub background: Option<String>,
|
||||
#[serde(rename = "fontStyle")]
|
||||
pub font_style: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
||||
pub enum ZedSyntaxToken {
|
||||
Attribute,
|
||||
Boolean,
|
||||
Comment,
|
||||
CommentDoc,
|
||||
Constant,
|
||||
Constructor,
|
||||
Embedded,
|
||||
Emphasis,
|
||||
EmphasisStrong,
|
||||
Enum,
|
||||
Function,
|
||||
Hint,
|
||||
Keyword,
|
||||
Label,
|
||||
LinkText,
|
||||
LinkUri,
|
||||
Number,
|
||||
Operator,
|
||||
Predictive,
|
||||
Preproc,
|
||||
Primary,
|
||||
Property,
|
||||
Punctuation,
|
||||
PunctuationBracket,
|
||||
PunctuationDelimiter,
|
||||
PunctuationListMarker,
|
||||
PunctuationSpecial,
|
||||
String,
|
||||
StringEscape,
|
||||
StringRegex,
|
||||
StringSpecial,
|
||||
StringSpecialSymbol,
|
||||
Tag,
|
||||
TextLiteral,
|
||||
Title,
|
||||
Type,
|
||||
Variable,
|
||||
VariableSpecial,
|
||||
Variant,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ZedSyntaxToken {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
ZedSyntaxToken::Attribute => "attribute",
|
||||
ZedSyntaxToken::Boolean => "boolean",
|
||||
ZedSyntaxToken::Comment => "comment",
|
||||
ZedSyntaxToken::CommentDoc => "comment.doc",
|
||||
ZedSyntaxToken::Constant => "constant",
|
||||
ZedSyntaxToken::Constructor => "constructor",
|
||||
ZedSyntaxToken::Embedded => "embedded",
|
||||
ZedSyntaxToken::Emphasis => "emphasis",
|
||||
ZedSyntaxToken::EmphasisStrong => "emphasis.strong",
|
||||
ZedSyntaxToken::Enum => "enum",
|
||||
ZedSyntaxToken::Function => "function",
|
||||
ZedSyntaxToken::Hint => "hint",
|
||||
ZedSyntaxToken::Keyword => "keyword",
|
||||
ZedSyntaxToken::Label => "label",
|
||||
ZedSyntaxToken::LinkText => "link_text",
|
||||
ZedSyntaxToken::LinkUri => "link_uri",
|
||||
ZedSyntaxToken::Number => "number",
|
||||
ZedSyntaxToken::Operator => "operator",
|
||||
ZedSyntaxToken::Predictive => "predictive",
|
||||
ZedSyntaxToken::Preproc => "preproc",
|
||||
ZedSyntaxToken::Primary => "primary",
|
||||
ZedSyntaxToken::Property => "property",
|
||||
ZedSyntaxToken::Punctuation => "punctuation",
|
||||
ZedSyntaxToken::PunctuationBracket => "punctuation.bracket",
|
||||
ZedSyntaxToken::PunctuationDelimiter => "punctuation.delimiter",
|
||||
ZedSyntaxToken::PunctuationListMarker => "punctuation.list_marker",
|
||||
ZedSyntaxToken::PunctuationSpecial => "punctuation.special",
|
||||
ZedSyntaxToken::String => "string",
|
||||
ZedSyntaxToken::StringEscape => "string.escape",
|
||||
ZedSyntaxToken::StringRegex => "string.regex",
|
||||
ZedSyntaxToken::StringSpecial => "string.special",
|
||||
ZedSyntaxToken::StringSpecialSymbol => "string.special.symbol",
|
||||
ZedSyntaxToken::Tag => "tag",
|
||||
ZedSyntaxToken::TextLiteral => "text.literal",
|
||||
ZedSyntaxToken::Title => "title",
|
||||
ZedSyntaxToken::Type => "type",
|
||||
ZedSyntaxToken::Variable => "variable",
|
||||
ZedSyntaxToken::VariableSpecial => "variable.special",
|
||||
ZedSyntaxToken::Variant => "variant",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZedSyntaxToken {
|
||||
pub fn find_best_token_color_match<'a>(
|
||||
&self,
|
||||
token_colors: &'a [VsCodeTokenColor],
|
||||
) -> Option<&'a VsCodeTokenColor> {
|
||||
let mut ranked_matches = IndexMap::new();
|
||||
|
||||
for (ix, token_color) in token_colors.iter().enumerate() {
|
||||
if token_color.settings.foreground.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(rank) = self.rank_match(token_color) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if rank > 0 {
|
||||
ranked_matches.insert(ix, rank);
|
||||
}
|
||||
}
|
||||
|
||||
ranked_matches
|
||||
.into_iter()
|
||||
.max_by_key(|(_, rank)| *rank)
|
||||
.map(|(ix, _)| &token_colors[ix])
|
||||
}
|
||||
|
||||
fn rank_match(&self, token_color: &VsCodeTokenColor) -> Option<u32> {
|
||||
let candidate_scopes = match token_color.scope.as_ref()? {
|
||||
VsCodeTokenScope::One(scope) => vec![scope],
|
||||
VsCodeTokenScope::Many(scopes) => scopes.iter().collect(),
|
||||
}
|
||||
.iter()
|
||||
.flat_map(|scope| scope.split(',').map(|s| s.trim()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let scopes_to_match = self.to_vscode();
|
||||
let number_of_scopes_to_match = scopes_to_match.len();
|
||||
|
||||
let mut matches = 0;
|
||||
|
||||
for (ix, scope) in scopes_to_match.into_iter().enumerate() {
|
||||
// Assign each entry a weight that is inversely proportional to its
|
||||
// position in the list.
|
||||
//
|
||||
// Entries towards the front are weighted higher than those towards the end.
|
||||
let weight = (number_of_scopes_to_match - ix) as u32;
|
||||
|
||||
if candidate_scopes.contains(&scope) {
|
||||
matches += 1 + weight;
|
||||
}
|
||||
}
|
||||
|
||||
Some(matches)
|
||||
}
|
||||
|
||||
pub fn fallbacks(&self) -> &[Self] {
|
||||
match self {
|
||||
ZedSyntaxToken::CommentDoc => &[ZedSyntaxToken::Comment],
|
||||
ZedSyntaxToken::Number => &[ZedSyntaxToken::Constant],
|
||||
ZedSyntaxToken::VariableSpecial => &[ZedSyntaxToken::Variable],
|
||||
ZedSyntaxToken::PunctuationBracket
|
||||
| ZedSyntaxToken::PunctuationDelimiter
|
||||
| ZedSyntaxToken::PunctuationListMarker
|
||||
| ZedSyntaxToken::PunctuationSpecial => &[ZedSyntaxToken::Punctuation],
|
||||
ZedSyntaxToken::StringEscape
|
||||
| ZedSyntaxToken::StringRegex
|
||||
| ZedSyntaxToken::StringSpecial
|
||||
| ZedSyntaxToken::StringSpecialSymbol => &[ZedSyntaxToken::String],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vscode(self) -> Vec<&'static str> {
|
||||
match self {
|
||||
ZedSyntaxToken::Attribute => vec!["entity.other.attribute-name"],
|
||||
ZedSyntaxToken::Boolean => vec!["constant.language"],
|
||||
ZedSyntaxToken::Comment => vec!["comment"],
|
||||
ZedSyntaxToken::CommentDoc => vec!["comment.block.documentation"],
|
||||
ZedSyntaxToken::Constant => vec!["constant", "constant.language", "constant.character"],
|
||||
ZedSyntaxToken::Constructor => {
|
||||
vec![
|
||||
"entity.name.tag",
|
||||
"entity.name.function.definition.special.constructor",
|
||||
]
|
||||
}
|
||||
ZedSyntaxToken::Embedded => vec!["meta.embedded"],
|
||||
ZedSyntaxToken::Emphasis => vec!["markup.italic"],
|
||||
ZedSyntaxToken::EmphasisStrong => vec![
|
||||
"markup.bold",
|
||||
"markup.italic markup.bold",
|
||||
"markup.bold markup.italic",
|
||||
],
|
||||
ZedSyntaxToken::Enum => vec!["support.type.enum"],
|
||||
ZedSyntaxToken::Function => vec![
|
||||
"entity.function",
|
||||
"entity.name.function",
|
||||
"variable.function",
|
||||
],
|
||||
ZedSyntaxToken::Hint => vec![],
|
||||
ZedSyntaxToken::Keyword => vec![
|
||||
"keyword",
|
||||
"keyword.other.fn.rust",
|
||||
"keyword.control",
|
||||
"keyword.control.fun",
|
||||
"keyword.control.class",
|
||||
"punctuation.accessor",
|
||||
"entity.name.tag",
|
||||
],
|
||||
ZedSyntaxToken::Label => vec![
|
||||
"label",
|
||||
"entity.name",
|
||||
"entity.name.import",
|
||||
"entity.name.package",
|
||||
],
|
||||
ZedSyntaxToken::LinkText => vec!["markup.underline.link", "string.other.link"],
|
||||
ZedSyntaxToken::LinkUri => vec!["markup.underline.link", "string.other.link"],
|
||||
ZedSyntaxToken::Number => vec!["constant.numeric", "number"],
|
||||
ZedSyntaxToken::Operator => vec!["operator", "keyword.operator"],
|
||||
ZedSyntaxToken::Predictive => vec![],
|
||||
ZedSyntaxToken::Preproc => vec![
|
||||
"preproc",
|
||||
"meta.preprocessor",
|
||||
"punctuation.definition.preprocessor",
|
||||
],
|
||||
ZedSyntaxToken::Primary => vec![],
|
||||
ZedSyntaxToken::Property => vec![
|
||||
"variable.member",
|
||||
"support.type.property-name",
|
||||
"variable.object.property",
|
||||
"variable.other.field",
|
||||
],
|
||||
ZedSyntaxToken::Punctuation => vec![
|
||||
"punctuation",
|
||||
"punctuation.section",
|
||||
"punctuation.accessor",
|
||||
"punctuation.separator",
|
||||
"punctuation.definition.tag",
|
||||
],
|
||||
ZedSyntaxToken::PunctuationBracket => vec![
|
||||
"punctuation.bracket",
|
||||
"punctuation.definition.tag.begin",
|
||||
"punctuation.definition.tag.end",
|
||||
],
|
||||
ZedSyntaxToken::PunctuationDelimiter => vec![
|
||||
"punctuation.delimiter",
|
||||
"punctuation.separator",
|
||||
"punctuation.terminator",
|
||||
],
|
||||
ZedSyntaxToken::PunctuationListMarker => {
|
||||
vec!["markup.list punctuation.definition.list.begin"]
|
||||
}
|
||||
ZedSyntaxToken::PunctuationSpecial => vec!["punctuation.special"],
|
||||
ZedSyntaxToken::String => vec!["string"],
|
||||
ZedSyntaxToken::StringEscape => {
|
||||
vec!["string.escape", "constant.character", "constant.other"]
|
||||
}
|
||||
ZedSyntaxToken::StringRegex => vec!["string.regex"],
|
||||
ZedSyntaxToken::StringSpecial => vec!["string.special", "constant.other.symbol"],
|
||||
ZedSyntaxToken::StringSpecialSymbol => {
|
||||
vec!["string.special.symbol", "constant.other.symbol"]
|
||||
}
|
||||
ZedSyntaxToken::Tag => vec!["tag", "entity.name.tag", "meta.tag.sgml"],
|
||||
ZedSyntaxToken::TextLiteral => vec!["text.literal", "string"],
|
||||
ZedSyntaxToken::Title => vec!["title", "entity.name"],
|
||||
ZedSyntaxToken::Type => vec![
|
||||
"entity.name.type",
|
||||
"entity.name.type.primitive",
|
||||
"entity.name.type.numeric",
|
||||
"keyword.type",
|
||||
"support.type",
|
||||
"support.type.primitive",
|
||||
"support.class",
|
||||
],
|
||||
ZedSyntaxToken::Variable => vec![
|
||||
"variable",
|
||||
"variable.language",
|
||||
"variable.member",
|
||||
"variable.parameter",
|
||||
"variable.parameter.function-call",
|
||||
],
|
||||
ZedSyntaxToken::VariableSpecial => vec![
|
||||
"variable.special",
|
||||
"variable.member",
|
||||
"variable.annotation",
|
||||
"variable.language",
|
||||
],
|
||||
ZedSyntaxToken::Variant => vec!["variant"],
|
||||
}
|
||||
}
|
||||
}
|
||||
40
crates/theme_importer/src/vscode/theme.rs
Normal file
40
crates/theme_importer/src/vscode/theme.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use serde::Deserialize;
|
||||
use vscode_theme::Colors;
|
||||
|
||||
use crate::vscode::VsCodeTokenColor;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct VsCodeTheme {
|
||||
#[serde(rename = "$schema")]
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
|
||||
)]
|
||||
pub schema: Option<String>,
|
||||
pub name: Option<String>,
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
|
||||
)]
|
||||
pub author: Option<String>,
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
|
||||
)]
|
||||
pub maintainers: Option<Vec<String>>,
|
||||
#[serde(rename = "semanticClass")]
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
|
||||
)]
|
||||
pub semantic_class: Option<String>,
|
||||
#[expect(
|
||||
unused,
|
||||
reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
|
||||
)]
|
||||
#[serde(rename = "semanticHighlighting")]
|
||||
pub semantic_highlighting: Option<bool>,
|
||||
pub colors: Colors,
|
||||
#[serde(rename = "tokenColors")]
|
||||
pub token_colors: Vec<VsCodeTokenColor>,
|
||||
}
|
||||
Reference in New Issue
Block a user