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

View File

@ -5,7 +5,7 @@ use helix_view::{
graphics::{Color, CursorKind, Rect, UnderlineStyle},
input::{Event, KeyEvent, MouseButton, MouseEvent},
keyboard::{KeyCode, KeyModifiers},
theme::Style,
theme::{Modifier, Style},
Editor,
};
use steel::{
@ -258,12 +258,47 @@ pub fn helix_component_module() -> BuiltInModule {
.register_value("Color/LightCyan", Color::LightCyan.into_steelval().unwrap())
.register_value("Color/LightGray", Color::LightGray.into_steelval().unwrap())
.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("set-style-fg!", |style: &mut Style, color: Color| {
style.fg = Some(color);
})
.register_fn("style-fg", Style::fg)
.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->bg", |style: &Style| style.bg)
.register_fn("set-style-bg!", |style: &mut Style, color: Color| {

View File

@ -19,7 +19,8 @@ use helix_view::{
},
extension::document_id_to_usize,
input::KeyEvent,
DocumentId, Editor, ViewId,
theme::Color,
DocumentId, Editor, Theme, ViewId,
};
use once_cell::sync::{Lazy, OnceCell};
use steel::{
@ -31,6 +32,7 @@ use steel::{
steelerr, SteelErr, SteelVal,
};
use std::sync::Arc;
use std::{
borrow::Cow,
collections::HashMap,
@ -39,7 +41,6 @@ use std::{
sync::{atomic::AtomicBool, Mutex, MutexGuard},
time::Duration,
};
use std::{io::BufWriter, sync::Arc};
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);
}
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) {
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) {
load_editor_api(engine, generate_sources);
load_theme_api(engine, generate_sources);
load_configuration_api(engine, generate_sources);
load_typed_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.
cx.editor.unset_theme_preview();
} 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()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme_preview(theme);
};
}
};
}
PromptEvent::Validate => {
if let Some(theme_name) = args.first() {
let theme = cx
.editor
.theme_loader
.load(theme_name)
.map_err(|err| anyhow::anyhow!("Could not load theme: {}", err))?;
let 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()
})?;
// 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()) {
bail!("Unsupported theme: theme requires true color support");
}

View File

@ -295,7 +295,7 @@ pub mod completers {
.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"));
for rt_dir in helix_loader::runtime_dirs() {
names.extend(theme::Loader::read_names(&rt_dir.join("themes")));
@ -303,6 +303,10 @@ pub mod completers {
names.push("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.dedup();

View File

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

View File

@ -9,6 +9,7 @@ use helix_core::hashmap;
use helix_loader::merge_toml_values;
use log::warn;
use once_cell::sync::Lazy;
use rustix::path::Arg;
use serde::{Deserialize, Deserializer};
use toml::{map::Map, Value};
@ -307,10 +308,24 @@ impl Theme {
&self.name
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn get(&self, scope: &str) -> Style {
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
/// scopes. For example if `ui.text.focus` is not defined in the theme,
/// `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 {
Theme::from_keys(table)
} else {
@ -378,7 +393,7 @@ impl Theme {
}
}
struct ThemePalette {
pub struct ThemePalette {
palette: HashMap<String, Color>,
}