mirror of https://github.com/helix-editor/helix
instrument theme API
parent
0f8ab5f896
commit
7cbde094c4
|
@ -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",
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue