Skip to content

Commit

Permalink
wip: cloned theme loader as the icons loader and display the filetype…
Browse files Browse the repository at this point in the history
… icon in the file picker
  • Loading branch information
lazytanuki committed Jun 23, 2022
1 parent b365f2d commit 48a1d67
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 6 deletions.
27 changes: 27 additions & 0 deletions helix-loader/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,30 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {

Ok(config)
}

/// Default built-in icons.toml.
pub fn default_icons_config() -> toml::Value {
toml::from_slice(include_bytes!("../../icons.toml"))
.expect("Could not parse built-in icons.toml to valid toml")
}

/// User configured icons.toml file, merged with the default config.
pub fn user_icons_config() -> Result<toml::Value, toml::de::Error> {
let config = crate::local_config_dirs()
.into_iter()
.chain([crate::config_dir()].into_iter())
.map(|path| path.join("icons.toml"))
.filter_map(|file| {
std::fs::read(&file)
.map(|config| toml::from_slice(&config))
.ok()
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.chain([default_icons_config()].into_iter())
.fold(toml::Value::Table(toml::value::Table::default()), |a, b| {
crate::merge_toml_values(b, a, true)
});

Ok(config)
}
4 changes: 4 additions & 0 deletions helix-loader/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ pub fn log_file() -> std::path::PathBuf {
cache_dir().join("helix.log")
}

pub fn icons_config_file() -> std::path::PathBuf {
config_dir().join("icons.toml")
}

pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::path::PathBuf> {
let current_dir = std::env::current_dir().expect("unable to determine current directory");
let mut directories = Vec::new();
Expand Down
24 changes: 22 additions & 2 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use helix_core::{
pos_at_coords, syntax, Selection,
};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
use helix_view::{align_view, editor::ConfigEvent, theme, Align, Editor};
use helix_view::{align_view, editor::ConfigEvent, icons, theme, Align, Editor};
use serde_json::json;

use crate::{
Expand Down Expand Up @@ -116,6 +116,24 @@ impl Application {
}
});

let icons_loader = std::sync::Arc::new(icons::Loader::new(
&config_dir,
&helix_loader::runtime_dir(),
));
let icons = config
.icons
.as_ref()
.and_then(|icons| {
icons_loader
.load(icons)
.map_err(|e| {
log::warn!("failed to load icons `{}` - {}", icons, e);
e
})
.ok()
})
.unwrap_or_else(|| icons_loader.default());

let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
eprintln!("Bad language config: {}", err);
eprintln!("Press <ENTER> to continue with default language config");
Expand All @@ -131,6 +149,7 @@ impl Application {
let mut editor = Editor::new(
compositor.size(),
theme_loader.clone(),
icons_loader.clone(),
syn_loader.clone(),
Box::new(Map::new(Arc::clone(&config), |config: &Config| {
&config.editor
Expand All @@ -153,7 +172,7 @@ impl Application {
if first.is_dir() {
std::env::set_current_dir(&first).context("set current dir")?;
editor.new_file(Action::VerticalSplit);
let picker = ui::file_picker(".".into(), &config.load().editor);
let picker = ui::file_picker(".".into(), &config.load().editor, &icons);
compositor.push(Box::new(overlayed(picker)));
} else {
let nr_of_files = args.files.len();
Expand Down Expand Up @@ -194,6 +213,7 @@ impl Application {
}

editor.set_theme(theme);
editor.set_icons(icons);

#[cfg(windows)]
let signals = futures_util::stream::empty();
Expand Down
4 changes: 2 additions & 2 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2148,13 +2148,13 @@ fn append_mode(cx: &mut Context) {
fn file_picker(cx: &mut Context) {
// We don't specify language markers, root will be the root of the current git repo
let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
let picker = ui::file_picker(root, &cx.editor.config());
let picker = ui::file_picker(root, &cx.editor.config(), &cx.editor.icons);
cx.push_layer(Box::new(overlayed(picker)));
}

fn file_picker_in_current_directory(cx: &mut Context) {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
let picker = ui::file_picker(cwd, &cx.editor.config());
let picker = ui::file_picker(cwd, &cx.editor.config(), &cx.editor.icons);
cx.push_layer(Box::new(overlayed(picker)));
}

Expand Down
2 changes: 2 additions & 0 deletions helix-term/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use toml::de::Error as TomlError;
#[serde(deny_unknown_fields)]
pub struct Config {
pub theme: Option<String>,
pub icons: Option<String>,
#[serde(default = "default")]
pub keys: HashMap<Mode, Keymap>,
#[serde(default)]
Expand All @@ -21,6 +22,7 @@ impl Default for Config {
fn default() -> Config {
Config {
theme: None,
icons: None,
keys: default(),
editor: helix_view::editor::Config::default(),
}
Expand Down
6 changes: 6 additions & 0 deletions helix-term/src/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub fn general() -> std::io::Result<()> {
let lang_file = helix_loader::lang_config_file();
let log_file = helix_loader::log_file();
let rt_dir = helix_loader::runtime_dir();
let icons_file = helix_loader::icons_config_file();

if config_file.exists() {
writeln!(stdout, "Config file: {}", config_file.display())?;
Expand All @@ -63,6 +64,11 @@ pub fn general() -> std::io::Result<()> {
} else {
writeln!(stdout, "Language file: default")?;
}
if icons_file.exists() {
writeln!(stdout, "Icons file: {}", icons_file.display())?;
} else {
writeln!(stdout, "Icons file: default")?;
}
writeln!(stdout, "Log file: {}", log_file.display())?;
writeln!(stdout, "Runtime directory: {}", rt_dir.display())?;

Expand Down
18 changes: 16 additions & 2 deletions helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod text;

pub use completion::Completion;
pub use editor::EditorView;
use helix_view::icons::Icons;
pub use markdown::Markdown;
pub use menu::Menu;
pub use picker::{FileLocation, FilePicker, Picker};
Expand Down Expand Up @@ -107,7 +108,11 @@ pub fn regex_prompt(
cx.push_layer(Box::new(prompt));
}

pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker<PathBuf> {
pub fn file_picker(
root: PathBuf,
config: &helix_view::editor::Config,
icons: &Icons,
) -> FilePicker<PathBuf> {
use ignore::{types::TypesBuilder, WalkBuilder};
use std::time::Instant;

Expand Down Expand Up @@ -166,13 +171,22 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
files.take(MAX).collect()
};

let filetype_icons_enabled = config.file_picker.filetype_icons;
let icons = icons.clone();

log::debug!("file_picker init {:?}", Instant::now().duration_since(now));

FilePicker::new(
files,
move |path: &PathBuf| {
// format_fn
path.strip_prefix(&root).unwrap_or(path).to_string_lossy()
let stripped_path = path.strip_prefix(&root).unwrap_or(path).to_string_lossy();
if filetype_icons_enabled {
if let Some(icon) = icons.mimetype_icon_for_path(path) {
return format!("{} {}", icon, stripped_path).into();
}
}
stripped_path
},
move |cx, path: &PathBuf, action| {
if let Err(e) = cx.editor.open(path, action) {
Expand Down
14 changes: 14 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::{CursorKind, Rect},
icons::{self, Icons},
info::Info,
input::KeyEvent,
theme::{self, Theme},
Expand Down Expand Up @@ -91,6 +92,8 @@ pub struct FilePickerConfig {
/// WalkBuilder options
/// Maximum Depth to recurse directories in file picker and global search. Defaults to `None`.
pub max_depth: Option<usize>,
/// Enables filetype icons.
pub filetype_icons: bool,
}

impl Default for FilePickerConfig {
Expand All @@ -104,6 +107,7 @@ impl Default for FilePickerConfig {
git_global: true,
git_exclude: true,
max_depth: None,
filetype_icons: true,
}
}
}
Expand Down Expand Up @@ -458,6 +462,7 @@ pub struct Editor {
pub macro_recording: Option<(char, Vec<KeyEvent>)>,
pub macro_replaying: Vec<char>,
pub theme: Theme,
pub icons: Icons,
pub language_servers: helix_lsp::Registry,

pub debugger: Option<dap::Client>,
Expand All @@ -468,6 +473,7 @@ pub struct Editor {

pub syn_loader: Arc<syntax::Loader>,
pub theme_loader: Arc<theme::Loader>,
pub icons_loader: Arc<icons::Loader>,

pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>,
Expand Down Expand Up @@ -510,6 +516,7 @@ impl Editor {
pub fn new(
mut area: Rect,
theme_loader: Arc<theme::Loader>,
icons_loader: Arc<icons::Loader>,
syn_loader: Arc<syntax::Loader>,
config: Box<dyn DynAccess<Config>>,
) -> Self {
Expand All @@ -529,12 +536,14 @@ impl Editor {
macro_recording: None,
macro_replaying: Vec::new(),
theme: theme_loader.default(),
icons: icons_loader.default(),
language_servers,
debugger: None,
debugger_events: SelectAll::new(),
breakpoints: HashMap::new(),
syn_loader,
theme_loader,
icons_loader,
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
Expand Down Expand Up @@ -618,6 +627,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<()> {
let doc = self.documents.get_mut(&doc_id)?;
Expand Down
107 changes: 107 additions & 0 deletions helix-view/src/icons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use anyhow::Context;
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Diagnostic {
pub error: char,
pub warning: char,
pub info: char,
pub notice: char,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct SymbolKind {
pub variable: char,
pub function: char,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Icons {
mime_type: HashMap<String, char>,
pub diagnostic: Diagnostic,
pub symbol_kind: SymbolKind,
}

pub struct Loader {
user_dir: PathBuf,
default_dir: PathBuf,
}

pub static DEFAULT_ICONS: Lazy<Icons> = Lazy::new(|| {
toml::from_slice(include_bytes!("../../icons.toml")).expect("Failed to parse default icons")
});

impl Icons {
pub fn mimetype_icon_for_path(&self, path: &Path) -> Option<&char> {
if let Some(extension) = path.extension().and_then(|e| e.to_str()) {
self.mime_type.get(extension)
} else {
if let Some(filename) = path.file_name().and_then(|f| f.to_str()) {
self.mime_type.get(filename)
} else {
None
}
}
}
}

impl Loader {
/// Creates a new loader that can load icons flavors from two directories.
pub fn new<P: AsRef<Path>>(user_dir: P, default_dir: P) -> Self {
Self {
user_dir: user_dir.as_ref().join("icons"),
default_dir: default_dir.as_ref().join("icons"),
}
}

/// Loads icons flavors first looking in the `user_dir` then in `default_dir`
pub fn load(&self, name: &str) -> Result<Icons, anyhow::Error> {
if name == "default" {
return Ok(self.default());
}
let filename = format!("{}.toml", name);

let user_path = self.user_dir.join(&filename);
let path = if user_path.exists() {
user_path
} else {
self.default_dir.join(filename)
};

let data = std::fs::read(&path)?;
toml::from_slice(data.as_slice()).context("Failed to deserialize icon")
}

pub fn read_names(path: &Path) -> Vec<String> {
std::fs::read_dir(path)
.map(|entries| {
entries
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
(path.extension()? == "toml")
.then(|| path.file_stem().unwrap().to_string_lossy().into_owned())
})
.collect()
})
.unwrap_or_default()
}

/// Lists all icons flavors names available in default and user directory
pub fn names(&self) -> Vec<String> {
let mut names = Self::read_names(&self.user_dir);
names.extend(Self::read_names(&self.default_dir));
names
}

/// Returns the default icon flavor
pub fn default(&self) -> Icons {
DEFAULT_ICONS.clone()
}
}
1 change: 1 addition & 0 deletions helix-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod handlers {
pub mod dap;
pub mod lsp;
}
pub mod icons;
pub mod info;
pub mod input;
pub mod keyboard;
Expand Down
Loading

0 comments on commit 48a1d67

Please sign in to comment.