feat: add icons launch and runtime loading

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

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

@ -853,6 +853,30 @@ fn theme(
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(
cx: &mut compositor::Context,
_args: &[Cow<str>],
@ -2374,6 +2398,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: 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 {
name: "clipboard-yank",
aliases: &[],

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

@ -310,6 +310,37 @@ pub mod completers {
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
fn get_keys(value: &serde_json::Value, vec: &mut Vec<String>, scope: Option<&str>) {
if let Some(map) = value.as_object() {

@ -3,6 +3,7 @@ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode},
graphics::{CursorKind, Rect},
icons::{self, Icons},
info::Info,
input::KeyEvent,
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)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
@ -284,6 +306,8 @@ pub struct Config {
pub soft_wrap: SoftWrap,
/// Workspace specific lsp ceiling dirs
pub workspace_lsp_roots: Vec<PathBuf>,
/// Icons configuration
pub icons: IconsConfig,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -753,6 +777,7 @@ impl Default for Config {
text_width: 80,
completion_replace: false,
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
/// is set here.
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
/// restored when the preview is aborted, or added to the jumplist when it is
@ -927,11 +954,14 @@ impl Editor {
pub fn new(
mut area: Rect,
theme_loader: Arc<theme::Loader>,
icons_loader: Arc<icons::Loader>,
syn_loader: Arc<syntax::Loader>,
config: Arc<dyn DynAccess<Config>>,
) -> Self {
let conf = config.load();
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
area.height -= 1;
@ -974,6 +1004,8 @@ impl Editor {
needs_redraw: false,
cursor_cache: Cell::new(None),
completion_request_handle: None,
icons,
icons_loader,
}
}
@ -1074,6 +1106,9 @@ impl Editor {
}
ThemeAction::Set => {
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;
}
}
@ -1081,6 +1116,11 @@ impl Editor {
self._refresh();
}
pub fn set_icons(&mut self, icons: Icons) {
self.icons = icons;
self._refresh();
}
/// Refreshes the language server for a given document
pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> {
self.launch_language_server(doc_id)

@ -3,10 +3,7 @@ use log::warn;
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::{
path::{Path, PathBuf},
str,
};
use std::{path::PathBuf, str};
use toml::Value;
use crate::graphics::{Color, Style};
@ -220,7 +217,7 @@ pub static DEFAULT_ICONS: Lazy<Icons> = Lazy::new(|| Icons {
impl Loader {
/// 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 {
icons_dirs: dirs.iter().map(|p| p.join("icons")).collect(),
}

Loading…
Cancel
Save