instrument theme API

pull/8675/merge^2
Matt Paras 2024-11-12 21:30:45 -08:00
parent 0f8ab5f896
commit 7cbde094c4
8 changed files with 216 additions and 18 deletions

42
Cargo.lock generated
View File

@ -246,6 +246,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.31" version = "1.1.31"
@ -318,6 +327,21 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "compact_str"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"serde",
"static_assertions",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@ -2570,6 +2594,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustversion"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.18"
@ -2793,7 +2823,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "steel-core" name = "steel-core"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/mattwparas/steel.git#cf7a3df2c1cf0b0e1df53e127512f9fbde48476a" source = "git+https://github.com/mattwparas/steel.git#c89f53b6336c8eb3592328946149b8ebbd43b05a"
dependencies = [ dependencies = [
"abi_stable", "abi_stable",
"anyhow", "anyhow",
@ -2802,6 +2832,7 @@ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
"codespan-reporting", "codespan-reporting",
"compact_str",
"crossbeam", "crossbeam",
"dirs", "dirs",
"futures-executor", "futures-executor",
@ -2842,7 +2873,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-derive" name = "steel-derive"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/mattwparas/steel.git#cf7a3df2c1cf0b0e1df53e127512f9fbde48476a" source = "git+https://github.com/mattwparas/steel.git#c89f53b6336c8eb3592328946149b8ebbd43b05a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2852,7 +2883,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-doc" name = "steel-doc"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/mattwparas/steel.git#cf7a3df2c1cf0b0e1df53e127512f9fbde48476a" source = "git+https://github.com/mattwparas/steel.git#c89f53b6336c8eb3592328946149b8ebbd43b05a"
dependencies = [ dependencies = [
"steel-core", "steel-core",
] ]
@ -2860,7 +2891,7 @@ dependencies = [
[[package]] [[package]]
name = "steel-gen" name = "steel-gen"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/mattwparas/steel.git#cf7a3df2c1cf0b0e1df53e127512f9fbde48476a" source = "git+https://github.com/mattwparas/steel.git#c89f53b6336c8eb3592328946149b8ebbd43b05a"
dependencies = [ dependencies = [
"codegen", "codegen",
"serde", "serde",
@ -2870,8 +2901,9 @@ dependencies = [
[[package]] [[package]]
name = "steel-parser" name = "steel-parser"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/mattwparas/steel.git#cf7a3df2c1cf0b0e1df53e127512f9fbde48476a" source = "git+https://github.com/mattwparas/steel.git#c89f53b6336c8eb3592328946149b8ebbd43b05a"
dependencies = [ dependencies = [
"compact_str",
"fxhash", "fxhash",
"lasso", "lasso",
"num", "num",

View File

@ -5,7 +5,7 @@ use helix_view::{
graphics::{Color, CursorKind, Rect, UnderlineStyle}, graphics::{Color, CursorKind, Rect, UnderlineStyle},
input::{Event, KeyEvent, MouseButton, MouseEvent}, input::{Event, KeyEvent, MouseButton, MouseEvent},
keyboard::{KeyCode, KeyModifiers}, keyboard::{KeyCode, KeyModifiers},
theme::Style, theme::{Modifier, Style},
Editor, Editor,
}; };
use steel::{ use steel::{
@ -258,12 +258,47 @@ pub fn helix_component_module() -> BuiltInModule {
.register_value("Color/LightCyan", Color::LightCyan.into_steelval().unwrap()) .register_value("Color/LightCyan", Color::LightCyan.into_steelval().unwrap())
.register_value("Color/LightGray", Color::LightGray.into_steelval().unwrap()) .register_value("Color/LightGray", Color::LightGray.into_steelval().unwrap())
.register_fn("Color/rgb", Color::Rgb) .register_fn("Color/rgb", Color::Rgb)
.register_fn("Color-red", Color::red)
.register_fn("Color-green", Color::green)
.register_fn("Color-blue", Color::blue)
.register_fn("Color/Indexed", Color::Indexed) .register_fn("Color/Indexed", Color::Indexed)
.register_fn("set-style-fg!", |style: &mut Style, color: Color| { .register_fn("set-style-fg!", |style: &mut Style, color: Color| {
style.fg = Some(color); style.fg = Some(color);
}) })
.register_fn("style-fg", Style::fg) .register_fn("style-fg", Style::fg)
.register_fn("style-bg", Style::bg) .register_fn("style-bg", Style::bg)
.register_fn("style-with-italics", |style: &Style| {
let patch = Style::default().add_modifier(Modifier::ITALIC);
style.patch(patch)
})
.register_fn("style-with-bold", |style: Style| {
let patch = Style::default().add_modifier(Modifier::BOLD);
style.patch(patch)
})
.register_fn("style-with-dim", |style: &Style| {
let patch = Style::default().add_modifier(Modifier::DIM);
style.patch(patch)
})
.register_fn("style-with-slow-blink", |style: Style| {
let patch = Style::default().add_modifier(Modifier::SLOW_BLINK);
style.patch(patch)
})
.register_fn("style-with-rapid-blink", |style: Style| {
let patch = Style::default().add_modifier(Modifier::RAPID_BLINK);
style.patch(patch)
})
.register_fn("style-with-reversed", |style: Style| {
let patch = Style::default().add_modifier(Modifier::REVERSED);
style.patch(patch)
})
.register_fn("style-with-hidden", |style: Style| {
let patch = Style::default().add_modifier(Modifier::HIDDEN);
style.patch(patch)
})
.register_fn("style-with-crossed-out", |style: Style| {
let patch = Style::default().add_modifier(Modifier::CROSSED_OUT);
style.patch(patch)
})
.register_fn("style->fg", |style: &Style| style.fg) .register_fn("style->fg", |style: &Style| style.fg)
.register_fn("style->bg", |style: &Style| style.bg) .register_fn("style->bg", |style: &Style| style.bg)
.register_fn("set-style-bg!", |style: &mut Style, color: Color| { .register_fn("set-style-bg!", |style: &mut Style, color: Color| {

View File

@ -19,7 +19,8 @@ use helix_view::{
}, },
extension::document_id_to_usize, extension::document_id_to_usize,
input::KeyEvent, input::KeyEvent,
DocumentId, Editor, ViewId, theme::Color,
DocumentId, Editor, Theme, ViewId,
}; };
use once_cell::sync::{Lazy, OnceCell}; use once_cell::sync::{Lazy, OnceCell};
use steel::{ use steel::{
@ -31,6 +32,7 @@ use steel::{
steelerr, SteelErr, SteelVal, steelerr, SteelErr, SteelVal,
}; };
use std::sync::Arc;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashMap, collections::HashMap,
@ -39,7 +41,6 @@ use std::{
sync::{atomic::AtomicBool, Mutex, MutexGuard}, sync::{atomic::AtomicBool, Mutex, MutexGuard},
time::Duration, time::Duration,
}; };
use std::{io::BufWriter, sync::Arc};
use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule}; use steel::{rvals::Custom, steel_vm::builtin::BuiltInModule};
@ -850,6 +851,67 @@ fn load_configuration_api(engine: &mut Engine, generate_sources: bool) {
engine.register_module(module); engine.register_module(module);
} }
fn languages_api(engine: &mut Engine, generate_sources: bool) {
// TODO: Just look at the `cx.editor.syn_loader` for how to
// manipulate the languages bindings
todo!()
}
// TODO:
// This isn't the best API since it pretty much requires deserializing
// the whole theme model each time. While its not _horrible_, it is
// certainly not as efficient as it could be. If we could just edit
// the loaded theme in memory already, then it would be a bit nicer.
fn load_theme_api(engine: &mut Engine, generate_sources: bool) {
let mut module = BuiltInModule::new("helix/core/themes");
module
.register_fn("hashmap->theme", theme_from_json_string)
.register_fn("add-theme!", add_theme)
.register_fn("theme-style", get_style)
.register_fn("theme-set-style!", set_style)
.register_fn("string->color", string_to_color);
if generate_sources {
configure_lsp_builtins("themes", &module);
}
engine.register_module(module);
}
#[derive(Clone)]
struct SteelTheme(Theme);
impl Custom for SteelTheme {}
fn theme_from_json_string(name: String, value: SteelVal) -> Result<SteelTheme, anyhow::Error> {
// TODO: Really don't love this at all. The deserialization should be a bit more elegant
let json_value = serde_json::Value::try_from(value)?;
let value: toml::Value = serde_json::from_str(&serde_json::to_string(&json_value)?)?;
let (mut theme, _) = Theme::from_toml(value);
theme.set_name(name);
Ok(SteelTheme(theme))
}
// Mutate the theme?
fn add_theme(cx: &mut Context, theme: SteelTheme) {
cx.editor
.user_defined_themes
.insert(theme.0.name().to_owned(), theme.0);
}
fn get_style(theme: &SteelTheme, name: SteelString) -> helix_view::theme::Style {
theme.0.get(name.as_str()).clone()
}
fn set_style(theme: &mut SteelTheme, name: String, style: helix_view::theme::Style) {
theme.0.set(name, style)
}
fn string_to_color(string: SteelString) -> Result<Color, anyhow::Error> {
// TODO: Don't expose this directly
helix_view::theme::ThemePalette::string_to_rgb(string.as_str()).map_err(anyhow::Error::msg)
}
fn load_editor_api(engine: &mut Engine, generate_sources: bool) { fn load_editor_api(engine: &mut Engine, generate_sources: bool) {
let mut module = BuiltInModule::new("helix/core/editor"); let mut module = BuiltInModule::new("helix/core/editor");
@ -2192,6 +2254,7 @@ pub fn helix_runtime_search_path() -> PathBuf {
pub fn configure_builtin_sources(engine: &mut Engine, generate_sources: bool) { pub fn configure_builtin_sources(engine: &mut Engine, generate_sources: bool) {
load_editor_api(engine, generate_sources); load_editor_api(engine, generate_sources);
load_theme_api(engine, generate_sources);
load_configuration_api(engine, generate_sources); load_configuration_api(engine, generate_sources);
load_typed_commands(engine, generate_sources); load_typed_commands(engine, generate_sources);
load_static_commands(engine, generate_sources); load_static_commands(engine, generate_sources);

View File

@ -888,21 +888,42 @@ fn theme(
// Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty. // Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty.
cx.editor.unset_theme_preview(); cx.editor.unset_theme_preview();
} else if let Some(theme_name) = args.first() { } else if let Some(theme_name) = args.first() {
if let Ok(theme) = cx.editor.theme_loader.load(theme_name) { // if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
// if !(true_color || theme.is_16_color()) {
// bail!("Unsupported theme: theme requires true color support");
// }
// cx.editor.set_theme_preview(theme);
// };
if let Ok(theme) = cx.editor.theme_loader.load(theme_name).or_else(|_| {
cx.editor
.user_defined_themes
.get(theme_name.as_ref())
.ok_or_else(|| anyhow::anyhow!("Could not load theme"))
.cloned()
}) {
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }
cx.editor.set_theme_preview(theme); cx.editor.set_theme_preview(theme);
}; }
}; };
} }
PromptEvent::Validate => { PromptEvent::Validate => {
if let Some(theme_name) = args.first() { if let Some(theme_name) = args.first() {
let theme = cx let theme = cx.editor.theme_loader.load(theme_name).or_else(|_| {
.editor cx.editor
.theme_loader .user_defined_themes
.load(theme_name) .get(theme_name.as_ref())
.map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?; .ok_or_else(|| anyhow::anyhow!("Could not load theme"))
.cloned()
})?;
// let theme = cx
// .editor
// .theme_loader
// .load(theme_name)
// .map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?;
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
} }

View File

@ -295,7 +295,7 @@ pub mod completers {
.collect() .collect()
} }
pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn theme(editor: &Editor, input: &str) -> Vec<Completion> {
let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes")); let mut names = theme::Loader::read_names(&helix_loader::config_dir().join("themes"));
for rt_dir in helix_loader::runtime_dirs() { for rt_dir in helix_loader::runtime_dirs() {
names.extend(theme::Loader::read_names(&rt_dir.join("themes"))); names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
@ -303,6 +303,10 @@ pub mod completers {
names.push("default".into()); names.push("default".into());
names.push("base16_default".into()); names.push("base16_default".into());
// Include any user defined themes as well
names.extend(editor.user_defined_themes.keys().map(|x| x.into()));
names.sort(); names.sort();
names.dedup(); names.dedup();

View File

@ -1083,6 +1083,7 @@ pub struct Editor {
pub cursor_cache: CursorCache, pub cursor_cache: CursorCache,
pub editor_clipping: ClippingConfiguration, pub editor_clipping: ClippingConfiguration,
pub user_defined_themes: HashMap<String, Theme>,
} }
#[derive(Default)] #[derive(Default)]
@ -1210,6 +1211,7 @@ impl Editor {
mouse_down_range: None, mouse_down_range: None,
cursor_cache: CursorCache::default(), cursor_cache: CursorCache::default(),
editor_clipping: ClippingConfiguration::default(), editor_clipping: ClippingConfiguration::default(),
user_defined_themes: Default::default(),
} }
} }

View File

@ -292,6 +292,32 @@ impl From<Color> for crossterm::style::Color {
} }
} }
impl Color {
pub fn red(&self) -> Option<u8> {
if let Self::Rgb(r, _, _) = self {
Some(*r)
} else {
None
}
}
pub fn green(&self) -> Option<u8> {
if let Self::Rgb(_, g, _) = self {
Some(*g)
} else {
None
}
}
pub fn blue(&self) -> Option<u8> {
if let Self::Rgb(_, _, b) = self {
Some(*b)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnderlineStyle { pub enum UnderlineStyle {
Reset, Reset,

View File

@ -9,6 +9,7 @@ use helix_core::hashmap;
use helix_loader::merge_toml_values; use helix_loader::merge_toml_values;
use log::warn; use log::warn;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustix::path::Arg;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use toml::{map::Map, Value}; use toml::{map::Map, Value};
@ -307,10 +308,24 @@ impl Theme {
&self.name &self.name
} }
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn get(&self, scope: &str) -> Style { pub fn get(&self, scope: &str) -> Style {
self.try_get(scope).unwrap_or_default() self.try_get(scope).unwrap_or_default()
} }
pub fn set(&mut self, scope: String, style: Style) {
self.styles.insert(scope.to_string(), style);
for (name, highlights) in self.scopes.iter().zip(self.highlights.iter_mut()) {
if *name == scope {
*highlights = style;
}
}
}
/// Get the style of a scope, falling back to dot separated broader /// Get the style of a scope, falling back to dot separated broader
/// scopes. For example if `ui.text.focus` is not defined in the theme, /// scopes. For example if `ui.text.focus` is not defined in the theme,
/// `ui.text` is tried and then `ui` is tried. /// `ui.text` is tried and then `ui` is tried.
@ -356,7 +371,7 @@ impl Theme {
}) })
} }
fn from_toml(value: Value) -> (Self, Vec<String>) { pub fn from_toml(value: Value) -> (Self, Vec<String>) {
if let Value::Table(table) = value { if let Value::Table(table) = value {
Theme::from_keys(table) Theme::from_keys(table)
} else { } else {
@ -378,7 +393,7 @@ impl Theme {
} }
} }
struct ThemePalette { pub struct ThemePalette {
palette: HashMap<String, Color>, palette: HashMap<String, Color>,
} }