wip: add the `icons` module as well as default and nerdfonts flavors

pull/11/head
LazyTanuki 1 year ago
parent 55de407681
commit 63051a7163

@ -0,0 +1,299 @@
use helix_loader::merge_toml_values;
use log::warn;
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::{
path::{Path, PathBuf},
str,
};
use toml::Value;
use crate::graphics::{Color, Style};
use crate::Theme;
pub static BLANK_ICON: Icon = Icon {
icon_char: ' ',
style: None,
};
/// The style of an icon can either be defined by the TOML file, or by the theme.
/// We need to remember that in order to reload the icons colors when the theme changes.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IconStyle {
Custom(Style),
Default(Style),
}
impl Default for IconStyle {
fn default() -> Self {
IconStyle::Default(Style::default())
}
}
impl From<IconStyle> for Style {
fn from(icon_style: IconStyle) -> Self {
match icon_style {
IconStyle::Custom(style) => style,
IconStyle::Default(style) => style,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Icon {
#[serde(rename = "icon")]
pub icon_char: char,
#[serde(default)]
#[serde(deserialize_with = "icon_color_to_style", rename = "color")]
pub style: Option<IconStyle>,
}
impl Icon {
/// Loads a given style if the icon style is undefined or based on a default value
pub fn with_default_style(&mut self, style: Style) {
if self.style.is_none() || matches!(self.style, Some(IconStyle::Default(_))) {
self.style = Some(IconStyle::Default(style));
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Icons {
pub name: String,
pub mime_type: Option<HashMap<String, Icon>>,
pub diagnostic: Diagnostic,
pub symbol_kind: Option<HashMap<String, Icon>>,
pub breakpoint: Breakpoint,
pub diff: Diff,
pub ui: Option<HashMap<String, Icon>>,
}
impl Icons {
pub fn name(&self) -> &str {
&self.name
}
/// Set theme defined styles to diagnostic icons
pub fn set_diagnostic_icons_base_style(&mut self, theme: &Theme) {
self.diagnostic.error.with_default_style(theme.get("error"));
self.diagnostic.info.with_default_style(theme.get("info"));
self.diagnostic.hint.with_default_style(theme.get("hint"));
self.diagnostic
.warning
.with_default_style(theme.get("warning"));
}
/// Set theme defined styles to symbol-kind icons
pub fn set_symbolkind_icons_base_style(&mut self, theme: &Theme) {
let style = theme
.try_get("symbolkind")
.unwrap_or_else(|| theme.get("keyword"));
if let Some(symbol_kind_icons) = &mut self.symbol_kind {
for (_, icon) in symbol_kind_icons.iter_mut() {
icon.with_default_style(style);
}
}
}
/// Set the default style for all icons
pub fn reset_styles(&mut self) {
if let Some(mime_type_icons) = &mut self.mime_type {
for (_, icon) in mime_type_icons.iter_mut() {
icon.style = Some(IconStyle::Default(Style::default()));
}
}
if let Some(symbol_kind_icons) = &mut self.symbol_kind {
for (_, icon) in symbol_kind_icons.iter_mut() {
icon.style = Some(IconStyle::Default(Style::default()));
}
}
if let Some(ui_icons) = &mut self.ui {
for (_, icon) in ui_icons.iter_mut() {
icon.style = Some(IconStyle::Default(Style::default()));
}
}
self.diagnostic.error.style = Some(IconStyle::Default(Style::default()));
self.diagnostic.warning.style = Some(IconStyle::Default(Style::default()));
self.diagnostic.hint.style = Some(IconStyle::Default(Style::default()));
self.diagnostic.info.style = Some(IconStyle::Default(Style::default()));
}
pub fn icon_from_filetype<'a>(&'a self, filetype: &str) -> Option<&'a Icon> {
if let Some(mime_type_icons) = &self.mime_type {
mime_type_icons.get(filetype)
} else {
None
}
}
/// Try to return a reference to an appropriate icon for the specified file path, with a default "file" icon if none is found.
/// If no such "file" icon is available, return `None`.
pub fn icon_from_path<'a>(&'a self, filepath: Option<&PathBuf>) -> Option<&'a Icon> {
self.mime_type
.as_ref()
.and_then(|mime_type_icons| {
filepath?
.extension()
.or(filepath?.file_name())
.map(|extension_or_filename| extension_or_filename.to_str())?
.and_then(|extension_or_filename| mime_type_icons.get(extension_or_filename))
})
.or_else(|| self.ui.as_ref().and_then(|ui_icons| ui_icons.get("file")))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Diagnostic {
pub error: Icon,
pub warning: Icon,
pub info: Icon,
pub hint: Icon,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Breakpoint {
pub verified: Icon,
pub unverified: Icon,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Diff {
pub added: Icon,
pub deleted: Icon,
pub modified: Icon,
}
fn icon_color_to_style<'de, D>(deserializer: D) -> Result<Option<IconStyle>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let mut style = Style::default();
if !s.is_empty() {
match hex_string_to_rgb(&s) {
Ok(c) => {
style = style.fg(c);
}
Err(e) => {
log::error!("{}", e);
}
};
Ok(Some(IconStyle::Custom(style)))
} else {
Ok(None)
}
}
pub fn hex_string_to_rgb(s: &str) -> Result<Color, String> {
if s.starts_with('#') && s.len() >= 7 {
if let (Ok(red), Ok(green), Ok(blue)) = (
u8::from_str_radix(&s[1..3], 16),
u8::from_str_radix(&s[3..5], 16),
u8::from_str_radix(&s[5..7], 16),
) {
return Ok(Color::Rgb(red, green, blue));
}
}
Err(format!("Icon color: malformed hexcode: {}", s))
}
pub struct Loader {
/// Icons directories to search from highest to lowest priority
icons_dirs: Vec<PathBuf>,
}
pub static DEFAULT_ICONS_DATA: Lazy<Value> = Lazy::new(|| {
let bytes = include_bytes!("../../icons.toml");
toml::from_str(str::from_utf8(bytes).unwrap()).expect("Failed to parse base 16 default theme")
});
pub static DEFAULT_ICONS: Lazy<Icons> = Lazy::new(|| Icons {
name: "default".into(),
..Icons::from(DEFAULT_ICONS_DATA.clone())
});
impl Loader {
/// Creates a new loader that can load icons flavors from two directories.
pub fn new<P: AsRef<Path>>(dirs: &[PathBuf]) -> Self {
Self {
icons_dirs: dirs.iter().map(|p| p.join("icons")).collect(),
}
}
/// Loads icons flavors first looking in the `user_dir` then in `default_dir`.
/// The `theme` is needed in order to load default styles for diagnostic icons.
pub fn load(
&self,
name: &str,
theme: &Theme,
true_color: bool,
) -> Result<Icons, anyhow::Error> {
if name == "default" {
return Ok(self.default(theme));
}
let mut visited_paths = HashSet::new();
let default_icons = HashMap::from([("default", &DEFAULT_ICONS_DATA)]);
let mut icons = helix_loader::load_inheritable_toml(
name,
&self.icons_dirs,
&mut visited_paths,
&default_icons,
Self::merge_icons,
)
.map(Icons::from)?;
// Remove all styles when there is no truecolor support.
// Not classy, but less cumbersome than trying to pass a parameter to a deserializer.
if !true_color {
icons.reset_styles();
} else {
icons.set_diagnostic_icons_base_style(theme);
icons.set_symbolkind_icons_base_style(theme);
}
Ok(Icons {
name: name.into(),
..icons
})
}
fn merge_icons(parent: Value, child: Value) -> Value {
merge_toml_values(parent, child, 3)
}
/// Returns the default icon flavor.
/// The `theme` is needed in order to load default styles for diagnostic icons.
pub fn default(&self, theme: &Theme) -> Icons {
let mut icons = DEFAULT_ICONS.clone();
icons.set_diagnostic_icons_base_style(theme);
icons.set_symbolkind_icons_base_style(theme);
icons
}
}
impl From<Value> for Icons {
fn from(value: Value) -> Self {
if let Value::Table(mut table) = value {
// remove inherits from value to prevent errors
table.remove("inherits");
let toml_str = table.to_string();
match toml::from_str(&toml_str) {
Ok(icons) => icons,
Err(e) => {
log::error!("Failed to load icons, falling back to default: {}\n", e);
DEFAULT_ICONS.clone()
}
}
} else {
warn!("Expected icons TOML value to be a table, found {:?}", value);
DEFAULT_ICONS.clone()
}
}
}

@ -12,6 +12,7 @@ pub mod handlers {
pub mod lsp;
}
pub mod base64;
pub mod icons;
pub mod info;
pub mod input;
pub mod keyboard;

@ -0,0 +1,18 @@
name = "default"
# All icons here must be available as [default Unicode characters](https://en.wikipedia.org/wiki/List_of_Unicode_characters)
[diagnostic]
error = {icon = "●"}
warning = {icon = "●"}
info = {icon = "●"}
hint = {icon = "●"}
[breakpoint]
verified = {icon = "▲"}
unverified = {icon = "⊚"}
[diff]
added = {icon = "▍"}
deleted = {icon = "▔"}
modified = {icon = "▍"}

@ -0,0 +1,284 @@
name = "nerdfonts"
[diagnostic]
error = {icon = ""}
warning = {icon = ""}
info = {icon = ""}
hint = {icon = ""}
[breakpoint]
verified = {icon = "▲"}
unverified = {icon = "⊚"}
[diff]
added = {icon = "▍"}
deleted = {icon = "▔"}
modified = {icon = "▍"}
[symbol-kind]
file = {icon = ""}
module = {icon = ""}
namespace = {icon = ""}
package = {icon = ""}
class = {icon = "ﴯ"}
method = {icon = ""}
property = {icon = ""}
field = {icon = ""}
constructor = {icon = ""}
enumeration = {icon = ""}
interface = {icon = ""}
variable = {icon = ""}
function = {icon = ""}
constant = {icon = ""}
string = {icon = ""}
number = {icon = ""}
boolean = {icon = ""}
array = {icon = ""}
object = {icon = ""}
key = {icon = ""}
null = {icon = "ﳠ"}
enum-member = {icon = ""}
structure = {icon = "פּ"}
event = {icon = ""}
operator = {icon = ""}
type-parameter = {icon = ""}
[ui]
file = {icon = ""}
folder = {icon = ""}
folder_opened = {icon = ""}
git_branch = {icon = ""}
[mime-type]
# This is heavily based on https://github.com/nvim-tree/nvim-web-devicons
".babelrc" = { icon = "ﬥ", color = "#cbcb41" }
".bash_profile" = { icon = "", color = "#89e051" }
".bashrc" = { icon = "", color = "#89e051" }
".DS_Store" = { icon = "", color = "#41535b" }
".gitattributes" = { icon = "", color = "#41535b" }
".gitconfig" = { icon = "", color = "#41535b" }
".gitignore" = { icon = "", color = "#41535b" }
".gitlab-ci.yml" = { icon = "", color = "#e24329" }
".gitmodules" = { icon = "", color = "#41535b" }
".gvimrc" = { icon = "", color = "#019833" }
".npmignore" = { icon = "", color = "#E8274B" }
".npmrc" = { icon = "", color = "#E8274B" }
".settings.json" = { icon = "", color = "#854CC7" }
".vimrc" = { icon = "", color = "#019833" }
".zprofile" = { icon = "", color = "#89e051" }
".zshenv" = { icon = "", color = "#89e051" }
".zshrc" = { icon = "", color = "#89e051" }
"Brewfile" = { icon = "", color = "#701516" }
"CMakeLists.txt" = { icon = "", color = "#6d8086" }
"COMMIT_EDITMSG" = { icon = "", color = "#41535b" }
"COPYING" = { icon = "", color = "#cbcb41" }
"COPYING.LESSER" = { icon = "", color = "#cbcb41" }
"Dockerfile" = { icon = "", color = "#384d54" }
"Gemfile$" = { icon = "", color = "#701516" }
"LICENSE" = { icon = "", color = "#d0bf41" }
"R" = { icon = "ﳒ", color = "#358a5b" }
"Rmd" = { icon = "", color = "#519aba" }
"Vagrantfile$" = { icon = "", color = "#1563FF" }
"_gvimrc" = { icon = "", color = "#019833" }
"_vimrc" = { icon = "", color = "#019833" }
"ai" = { icon = "", color = "#cbcb41" }
"awk" = { icon = "", color = "#4d5a5e" }
"bash" = { icon = "", color = "#89e051" }
"bat" = { icon = "", color = "#C1F12E" }
"bmp" = { icon = "", color = "#a074c4" }
"c" = { icon = "", color = "#599eff" }
"c++" = { icon = "", color = "#f34b7d" }
"cbl" = { icon = "⚙", color = "#005ca5" }
"cc" = { icon = "", color = "#f34b7d" }
"cfg" = { icon = "", color = "#ECECEC" }
"clj" = { icon = "", color = "#8dc149" }
"cljc" = { icon = "", color = "#8dc149" }
"cljs" = { icon = "", color = "#519aba" }
"cljd" = { icon = "", color = "#519aba" }
"cmake" = { icon = "", color = "#6d8086" }
"cob" = { icon = "⚙", color = "#005ca5" }
"cobol" = { icon = "⚙", color = "#005ca5" }
"coffee" = { icon = "", color = "#cbcb41" }
"conf" = { icon = "", color = "#6d8086" }
"config.ru" = { icon = "", color = "#701516" }
"cp" = { icon = "", color = "#519aba" }
"cpp" = { icon = "", color = "#519aba" }
"cpy" = { icon = "⚙", color = "#005ca5" }
"cr" = { icon = "" }
"cs" = { icon = "", color = "#596706" }
"csh" = { icon = "", color = "#4d5a5e" }
"cson" = { icon = "", color = "#cbcb41" }
"css" = { icon = "", color = "#42a5f5" }
"csv" = { icon = "", color = "#89e051" }
"cxx" = { icon = "", color = "#519aba" }
"d" = { icon = "", color = "#427819" }
"dart" = { icon = "", color = "#03589C" }
"db" = { icon = "", color = "#dad8d8" }
"desktop" = { icon = "", color = "#563d7c" }
"diff" = { icon = "", color = "#41535b" }
"doc" = { icon = "", color = "#185abd" }
"dockerfile" = { icon = "", color = "#384d54" }
"drl" = { icon = "", color = "#ffafaf" }
"dropbox" = { icon = "", color = "#0061FE" }
"dump" = { icon = "", color = "#dad8d8" }
"edn" = { icon = "", color = "#519aba" }
"eex" = { icon = "", color = "#a074c4" }
"ejs" = { icon = "", color = "#cbcb41" }
"elm" = { icon = "", color = "#519aba" }
"epp" = { icon = "", color = "#FFA61A" }
"erb" = { icon = "", color = "#701516" }
"erl" = { icon = "", color = "#B83998" }
"ex" = { icon = "", color = "#a074c4" }
"exs" = { icon = "", color = "#a074c4" }
"f#" = { icon = "", color = "#519aba" }
"favicon.ico" = { icon = "", color = "#cbcb41" }
"fnl" = { icon = "🌜", color = "#fff3d7" }
"fish" = { icon = "", color = "#4d5a5e" }
"fs" = { icon = "", color = "#519aba" }
"fsi" = { icon = "", color = "#519aba" }
"fsscript" = { icon = "", color = "#519aba" }
"fsx" = { icon = "", color = "#519aba" }
"gd" = { icon = "", color = "#6d8086" }
"gemspec" = { icon = "", color = "#701516" }
"gif" = { icon = "", color = "#a074c4" }
"git" = { icon = "", color = "#F14C28" }
"glb" = { icon = "", color = "#FFB13B" }
"go" = { icon = "", color = "#519aba" }
"godot" = { icon = "", color = "#6d8086" }
"graphql" = { icon = "", color = "#e535ab" }
"gruntfile" = { icon = "", color = "#e37933" }
"gulpfile" = { icon = "", color = "#cc3e44" }
"h" = { icon = "", color = "#a074c4" }
"haml" = { icon = "", color = "#eaeae1" }
"hbs" = { icon = "", color = "#f0772b" }
"heex" = { icon = "", color = "#a074c4" }
"hh" = { icon = "", color = "#a074c4" }
"hpp" = { icon = "", color = "#a074c4" }
"hrl" = { icon = "", color = "#B83998" }
"hs" = { icon = "", color = "#a074c4" }
"htm" = { icon = "", color = "#e34c26" }
"html" = { icon = "", color = "#e44d26" }
"hxx" = { icon = "", color = "#a074c4" }
"ico" = { icon = "", color = "#cbcb41" }
"import" = { icon = "", color = "#ECECEC" }
"ini" = { icon = "", color = "#6d8086" }
"java" = { icon = "", color = "#cc3e44" }
"jl" = { icon = "", color = "#a270ba" }
"jpeg" = { icon = "", color = "#a074c4" }
"jpg" = { icon = "", color = "#a074c4" }
"js" = { icon = "", color = "#cbcb41" }
"json" = { icon = "", color = "#cbcb41" }
"json5" = { icon = "ﬥ", color = "#cbcb41" }
"jsx" = { icon = "", color = "#519aba" }
"ksh" = { icon = "", color = "#4d5a5e" }
"kt" = { icon = "", color = "#F88A02" }
"kts" = { icon = "", color = "#F88A02" }
"leex" = { icon = "", color = "#a074c4" }
"less" = { icon = "", color = "#563d7c" }
"lhs" = { icon = "", color = "#a074c4" }
"license" = { icon = "", color = "#cbcb41" }
"lua" = { icon = "", color = "#51a0cf" }
"luau" = { icon = "", color = "#51a0cf" }
"makefile" = { icon = "", color = "#6d8086" }
"markdown" = { icon = "", color = "#d74c4c" }
"material" = { icon = "", color = "#B83998" }
"md" = { icon = "", color = "#d74c4c" }
"mdx" = { icon = "", color = "#d74c4c" }
"mint" = { icon = "", color = "#87c095" }
"mix.lock" = { icon = "", color = "#a074c4" }
"mjs" = { icon = "", color = "#f1e05a" }
"ml" = { icon = "λ", color = "#e37933" }
"mli" = { icon = "λ", color = "#e37933" }
"mo" = { icon = "∞", color = "#9772FB" }
"mustache" = { icon = "", color = "#e37933" }
"nim" = { icon = "👑", color = "#f3d400" }
"nix" = { icon = "", color = "#7ebae4" }
"node_modules" = { icon = "", color = "#E8274B" }
"opus" = { icon = "", color = "#F88A02" }
"otf" = { icon = "", color = "#ECECEC" }
"package.json" = { icon = "", color = "#e8274b" }
"package-lock.json" = { icon = "", color = "#7a0d21" }
"pck" = { icon = "", color = "#6d8086" }
"pdf" = { icon = "", color = "#b30b00" }
"php" = { icon = "", color = "#a074c4" }
"pl" = { icon = "", color = "#519aba" }
"pm" = { icon = "", color = "#519aba" }
"png" = { icon = "", color = "#a074c4" }
"pp" = { icon = "", color = "#FFA61A" }
"ppt" = { icon = "", color = "#cb4a32" }
"pro" = { icon = "", color = "#e4b854" }
"Procfile" = { icon = "", color = "#a074c4" }
"ps1" = { icon = "", color = "#4d5a5e" }
"psb" = { icon = "", color = "#519aba" }
"psd" = { icon = "", color = "#519aba" }
"py" = { icon = "", color = "#ffbc03" }
"pyc" = { icon = "", color = "#ffe291" }
"pyd" = { icon = "", color = "#ffe291" }
"pyo" = { icon = "", color = "#ffe291" }
"query" = { icon = "", color = "#90a850" }
"r" = { icon = "ﳒ", color = "#358a5b" }
"rake" = { icon = "", color = "#701516" }
"rakefile" = { icon = "", color = "#701516" }
"rb" = { icon = "", color = "#701516" }
"rlib" = { icon = "", color = "#dea584" }
"rmd" = { icon = "", color = "#519aba" }
"rproj" = { icon = "鉶", color = "#358a5b" }
"rs" = { icon = "", color = "#dea584" }
"rss" = { icon = "", color = "#FB9D3B" }
"sass" = { icon = "", color = "#f55385" }
"sbt" = { icon = "", color = "#cc3e44" }
"scala" = { icon = "", color = "#cc3e44" }
"scm" = { icon = "ﬦ" }
"scss" = { icon = "", color = "#f55385" }
"sh" = { icon = "", color = "#4d5a5e" }
"sig" = { icon = "λ", color = "#e37933" }
"slim" = { icon = "", color = "#e34c26" }
"sln" = { icon = "", color = "#854CC7" }
"sml" = { icon = "λ", color = "#e37933" }
"sql" = { icon = "", color = "#dad8d8" }
"sqlite" = { icon = "", color = "#dad8d8" }
"sqlite3" = { icon = "", color = "#dad8d8" }
"styl" = { icon = "", color = "#8dc149" }
"sublime" = { icon = "", color = "#e37933" }
"suo" = { icon = "", color = "#854CC7" }
"sv" = { icon = "", color = "#019833" }
"svelte" = { icon = "", color = "#ff3e00" }
"svh" = { icon = "", color = "#019833" }
"svg" = { icon = "ﰟ", color = "#FFB13B" }
"swift" = { icon = "", color = "#e37933" }
"t" = { icon = "", color = "#519aba" }
"tbc" = { icon = "﯑", color = "#1e5cb3" }
"tcl" = { icon = "﯑", color = "#1e5cb3" }
"terminal" = { icon = "", color = "#31B53E" }
"tex" = { icon = "ﭨ", color = "#3D6117" }
"tf" = { icon = "", color = "#5F43E9" }
"tfvars" = { icon = "", color = "#5F43E9" }
"toml" = { icon = "", color = "#6d8086" }
"tres" = { icon = "", color = "#cbcb41" }
"ts" = { icon = "", color = "#519aba" }
"tscn" = { icon = "", color = "#a074c4" }
"tsx" = { icon = "", color = "#519aba" }
"twig" = { icon = "", color = "#8dc149" }
"txt" = { icon = "", color = "#89e051" }
"v" = { icon = "", color = "#019833" }
"vh" = { icon = "", color = "#019833" }
"vhd" = { icon = "", color = "#019833" }
"vhdl" = { icon = "", color = "#019833" }
"vim" = { icon = "", color = "#019833" }
"vue" = { icon = "﵂", color = "#8dc149" }
"webmanifest" = { icon = "", color = "#f1e05a" }
"webp" = { icon = "", color = "#a074c4" }
"webpack" = { icon = "ﰩ", color = "#519aba" }
"xcplayground" = { icon = "", color = "#e37933" }
"xls" = { icon = "", color = "#207245" }
"xml" = { icon = "謹", color = "#e37933" }
"xul" = { icon = "", color = "#e37933" }
"yaml" = { icon = "", color = "#6d8086" }
"yml" = { icon = "", color = "#6d8086" }
"zig" = { icon = "", color = "#f69a1b" }
"zsh" = { icon = "", color = "#89e051" }
"sol" = { icon = "ﲹ", color = "#519aba" }
".env" = { icon = "", color = "#faf743" }
"prisma" = { icon = "卑" }
"lock" = { icon = "", color = "#bbbbbb" }
"log" = { icon = "" }
Loading…
Cancel
Save