feat: add icons launch and runtime loading

pull/11/head
LazyTanuki 2 years ago
parent 63051a7163
commit cfcf2ff4ff

@ -11,7 +11,7 @@ use helix_view::{
document::DocumentSavedEventResult, document::DocumentSavedEventResult,
editor::{ConfigEvent, EditorEvent}, editor::{ConfigEvent, EditorEvent},
graphics::Rect, graphics::Rect,
theme, icons, theme,
tree::Layout, tree::Layout,
Align, Editor, Align, Editor,
}; };
@ -69,6 +69,7 @@ pub struct Application {
#[allow(dead_code)] #[allow(dead_code)]
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
icons_loader: Arc<icons::Loader>,
#[allow(dead_code)] #[allow(dead_code)]
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<syntax::Loader>,
@ -111,9 +112,9 @@ impl Application {
use helix_view::editor::Action; use helix_view::editor::Action;
let mut theme_parent_dirs = vec![helix_loader::config_dir()]; let mut theme_and_icons_parent_dirs = vec![helix_loader::config_dir()];
theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned()); theme_and_icons_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned());
let theme_loader = std::sync::Arc::new(theme::Loader::new(&theme_parent_dirs)); let theme_loader = std::sync::Arc::new(theme::Loader::new(&theme_and_icons_parent_dirs));
let true_color = config.editor.true_color || crate::true_color(); let true_color = config.editor.true_color || crate::true_color();
let theme = config let theme = config
@ -131,6 +132,21 @@ impl Application {
}) })
.unwrap_or_else(|| theme_loader.default_theme(true_color)); .unwrap_or_else(|| theme_loader.default_theme(true_color));
let icons_loader = std::sync::Arc::new(icons::Loader::new(&theme_and_icons_parent_dirs));
let icons = config
.icons
.as_ref()
.and_then(|icons| {
icons_loader
.load(icons, &theme, true_color)
.map_err(|e| {
log::warn!("failed to load icons `{}` - {}", icons, e);
e
})
.ok()
})
.unwrap_or_else(|| icons_loader.default(&theme));
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
#[cfg(not(feature = "integration"))] #[cfg(not(feature = "integration"))]
@ -146,12 +162,16 @@ impl Application {
let mut editor = Editor::new( let mut editor = Editor::new(
area, area,
theme_loader.clone(), theme_loader.clone(),
icons_loader.clone(),
syn_loader.clone(), syn_loader.clone(),
Arc::new(Map::new(Arc::clone(&config), |config: &Config| { Arc::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.editor &config.editor
})), })),
); );
editor.set_theme(theme);
editor.set_icons(icons);
let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| { let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.keys &config.keys
})); }));
@ -225,8 +245,6 @@ impl Application {
.unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit));
} }
editor.set_theme(theme);
#[cfg(windows)] #[cfg(windows)]
let signals = futures_util::stream::empty(); let signals = futures_util::stream::empty();
#[cfg(not(windows))] #[cfg(not(windows))]
@ -241,6 +259,7 @@ impl Application {
config, config,
theme_loader, theme_loader,
icons_loader,
syn_loader, syn_loader,
signals, signals,
@ -413,12 +432,27 @@ impl Application {
Ok(()) Ok(())
} }
/// Refresh icons after config change
fn refresh_icons(&mut self, config: &Config) -> Result<(), Error> {
if let Some(icons) = config.icons.clone() {
let true_color = config.editor.true_color || crate::true_color();
let icons = self
.icons_loader
.load(&icons, &self.editor.theme, true_color)
.map_err(|err| anyhow::anyhow!("Failed to load icons `{}`: {}", icons, err))?;
self.editor.set_icons(icons);
}
Ok(())
}
fn refresh_config(&mut self) { fn refresh_config(&mut self) {
let mut refresh_config = || -> Result<(), Error> { let mut refresh_config = || -> Result<(), Error> {
let default_config = Config::load_default() let default_config = Config::load_default()
.map_err(|err| anyhow::anyhow!("Failed to load config: {}", err))?; .map_err(|err| anyhow::anyhow!("Failed to load config: {}", err))?;
self.refresh_language_config()?; self.refresh_language_config()?;
self.refresh_theme(&default_config)?; self.refresh_theme(&default_config)?;
self.refresh_icons(&default_config)?;
// Store new config // Store new config
self.config.store(Arc::new(default_config)); self.config.store(Arc::new(default_config));
Ok(()) Ok(())

@ -853,6 +853,30 @@ fn theme(
Ok(()) Ok(())
} }
fn icons(
cx: &mut compositor::Context,
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
let true_color = cx.editor.config.load().true_color || crate::true_color();
if let PromptEvent::Validate = event {
if let Some(flavor_name) = args.first() {
let icons = cx
.editor
.icons_loader
.load(flavor_name, &cx.editor.theme, true_color)
.map_err(|err| anyhow!("Could not load icon flavor: {}", err))?;
cx.editor.set_icons(icons);
} else {
let name = cx.editor.icons.name().to_string();
cx.editor.set_status(name);
}
};
Ok(())
}
fn yank_main_selection_to_clipboard( fn yank_main_selection_to_clipboard(
cx: &mut compositor::Context, cx: &mut compositor::Context,
_args: &[Cow<str>], _args: &[Cow<str>],
@ -2374,6 +2398,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: theme, fun: theme,
signature: CommandSignature::positional(&[completers::theme]), signature: CommandSignature::positional(&[completers::theme]),
}, },
TypableCommand {
name: "icons",
aliases: &[],
doc: "Change the editor icon flavor (show current flavor if no name specified).",
fun: icons,
signature: CommandSignature::positional(&[completers::icons]),
},
TypableCommand { TypableCommand {
name: "clipboard-yank", name: "clipboard-yank",
aliases: &[], aliases: &[],

@ -12,6 +12,7 @@ use toml::de::Error as TomlError;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Config { pub struct Config {
pub theme: Option<String>, pub theme: Option<String>,
pub icons: Option<String>,
pub keys: HashMap<Mode, Keymap>, pub keys: HashMap<Mode, Keymap>,
pub editor: helix_view::editor::Config, pub editor: helix_view::editor::Config,
} }
@ -20,6 +21,7 @@ pub struct Config {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ConfigRaw { pub struct ConfigRaw {
pub theme: Option<String>, pub theme: Option<String>,
pub icons: Option<String>,
pub keys: Option<HashMap<Mode, Keymap>>, pub keys: Option<HashMap<Mode, Keymap>>,
pub editor: Option<toml::Value>, pub editor: Option<toml::Value>,
} }
@ -28,6 +30,7 @@ impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
theme: None, theme: None,
icons: None,
keys: keymap::default(), keys: keymap::default(),
editor: helix_view::editor::Config::default(), editor: helix_view::editor::Config::default(),
} }
@ -86,6 +89,7 @@ impl Config {
Config { Config {
theme: local.theme.or(global.theme), theme: local.theme.or(global.theme),
icons: local.icons.or(global.icons),
keys, keys,
editor, editor,
} }
@ -102,6 +106,7 @@ impl Config {
} }
Config { Config {
theme: config.theme, theme: config.theme,
icons: config.icons,
keys, keys,
editor: config.editor.map_or_else( editor: config.editor.map_or_else(
|| Ok(helix_view::editor::Config::default()), || Ok(helix_view::editor::Config::default()),

@ -310,6 +310,37 @@ pub mod completers {
names names
} }
pub fn icons(_editor: &Editor, input: &str) -> Vec<Completion> {
let mut names = helix_loader::read_toml_names(&helix_loader::config_dir().join("icons"));
for rt_dir in helix_loader::runtime_dirs() {
names.extend(helix_loader::read_toml_names(&rt_dir.join("icons")));
}
names.push("default".into());
names.sort();
names.dedup();
let mut names: Vec<_> = names
.into_iter()
.map(|name| ((0..), Cow::from(name)))
.collect();
let matcher = Matcher::default();
let mut matches: Vec<_> = names
.into_iter()
.filter_map(|(_range, name)| {
matcher.fuzzy_match(&name, input).map(|score| (name, score))
})
.collect();
matches.sort_unstable_by(|(name1, score1), (name2, score2)| {
(Reverse(*score1), name1).cmp(&(Reverse(*score2), name2))
});
names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
names
}
/// Recursive function to get all keys from this value and add them to vec /// Recursive function to get all keys from this value and add them to vec
fn get_keys(value: &serde_json::Value, vec: &mut Vec<String>, scope: Option<&str>) { fn get_keys(value: &serde_json::Value, vec: &mut Vec<String>, scope: Option<&str>) {
if let Some(map) = value.as_object() { if let Some(map) = value.as_object() {

@ -3,6 +3,7 @@ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider}, clipboard::{get_clipboard_provider, ClipboardProvider},
document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode}, document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode},
graphics::{CursorKind, Rect}, graphics::{CursorKind, Rect},
icons::{self, Icons},
info::Info, info::Info,
input::KeyEvent, input::KeyEvent,
theme::{self, Theme}, theme::{self, Theme},
@ -211,6 +212,27 @@ impl Default for FilePickerConfig {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct IconsConfig {
/// Enables icons in front of buffer names in bufferline. Defaults to `true`
pub bufferline: bool,
/// Enables icons in front of items in the picker. Defaults to `true`
pub picker: bool,
/// Enables icons in front of items in the statusline. Defaults to `true`
pub statusline: bool,
}
impl Default for IconsConfig {
fn default() -> Self {
Self {
bufferline: true,
picker: true,
statusline: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config { pub struct Config {
@ -284,6 +306,8 @@ pub struct Config {
pub soft_wrap: SoftWrap, pub soft_wrap: SoftWrap,
/// Workspace specific lsp ceiling dirs /// Workspace specific lsp ceiling dirs
pub workspace_lsp_roots: Vec<PathBuf>, pub workspace_lsp_roots: Vec<PathBuf>,
/// Icons configuration
pub icons: IconsConfig,
} }
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -753,6 +777,7 @@ impl Default for Config {
text_width: 80, text_width: 80,
completion_replace: false, completion_replace: false,
workspace_lsp_roots: Vec::new(), workspace_lsp_roots: Vec::new(),
icons: IconsConfig::default(),
} }
} }
} }
@ -829,6 +854,8 @@ pub struct Editor {
/// The currently applied editor theme. While previewing a theme, the previewed theme /// The currently applied editor theme. While previewing a theme, the previewed theme
/// is set here. /// is set here.
pub theme: Theme, pub theme: Theme,
pub icons: Icons,
pub icons_loader: Arc<icons::Loader>,
/// The primary Selection prior to starting a goto_line_number preview. This is /// The primary Selection prior to starting a goto_line_number preview. This is
/// restored when the preview is aborted, or added to the jumplist when it is /// restored when the preview is aborted, or added to the jumplist when it is
@ -927,11 +954,14 @@ impl Editor {
pub fn new( pub fn new(
mut area: Rect, mut area: Rect,
theme_loader: Arc<theme::Loader>, theme_loader: Arc<theme::Loader>,
icons_loader: Arc<icons::Loader>,
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<syntax::Loader>,
config: Arc<dyn DynAccess<Config>>, config: Arc<dyn DynAccess<Config>>,
) -> Self { ) -> Self {
let conf = config.load(); let conf = config.load();
let auto_pairs = (&conf.auto_pairs).into(); let auto_pairs = (&conf.auto_pairs).into();
let theme = theme_loader.default();
let icons = icons_loader.default(&theme);
// HAXX: offset the render area height by 1 to account for prompt/commandline // HAXX: offset the render area height by 1 to account for prompt/commandline
area.height -= 1; area.height -= 1;
@ -974,6 +1004,8 @@ impl Editor {
needs_redraw: false, needs_redraw: false,
cursor_cache: Cell::new(None), cursor_cache: Cell::new(None),
completion_request_handle: None, completion_request_handle: None,
icons,
icons_loader,
} }
} }
@ -1074,6 +1106,9 @@ impl Editor {
} }
ThemeAction::Set => { ThemeAction::Set => {
self.last_theme = None; self.last_theme = None;
// Reload the icons to apply default colors based on theme
self.icons.set_diagnostic_icons_base_style(&theme);
self.icons.set_symbolkind_icons_base_style(&theme);
self.theme = theme; self.theme = theme;
} }
} }
@ -1081,6 +1116,11 @@ impl Editor {
self._refresh(); self._refresh();
} }
pub fn set_icons(&mut self, icons: Icons) {
self.icons = icons;
self._refresh();
}
/// Refreshes the language server for a given document /// Refreshes the language server for a given document
pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> {
self.launch_language_server(doc_id) self.launch_language_server(doc_id)

@ -3,10 +3,7 @@ use log::warn;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Deserialize; use serde::Deserialize;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::{ use std::{path::PathBuf, str};
path::{Path, PathBuf},
str,
};
use toml::Value; use toml::Value;
use crate::graphics::{Color, Style}; use crate::graphics::{Color, Style};
@ -220,7 +217,7 @@ pub static DEFAULT_ICONS: Lazy<Icons> = Lazy::new(|| Icons {
impl Loader { impl Loader {
/// Creates a new loader that can load icons flavors from two directories. /// Creates a new loader that can load icons flavors from two directories.
pub fn new<P: AsRef<Path>>(dirs: &[PathBuf]) -> Self { pub fn new(dirs: &[PathBuf]) -> Self {
Self { Self {
icons_dirs: dirs.iter().map(|p| p.join("icons")).collect(), icons_dirs: dirs.iter().map(|p| p.join("icons")).collect(),
} }

Loading…
Cancel
Save