Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ Without the installer: `cargo install --git https://github.com/Neotoxic-off/Lim
`cargo build --release` and copy `target/release/lim` (`lim.exe`) onto your `PATH`.

Runs on Linux, macOS and Windows 10+ (Windows Terminal or the console, not mintty). A
[Nerd Font](https://www.nerdfonts.com) for the default icons (or `"iconset": "unicode"` / `"ascii"`). Truecolor is
[Nerd Font](https://www.nerdfonts.com) for the default icons; without one installed, lim starts with
`"iconset": "unicode"` (or set `"ascii"`). Truecolor is
detected; other terminals get the 256-color palette.

Optional, picked up when installed: `node`, `python3`, `btm`, `gdu`, and the TUI tools of `<Leader>t…`.
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Names and defaults follow AstroNvim.
| `cmdheight` | `0` | `0`: notifications and an on-demand command line; `1`: permanent command line |
| `clipboard` | `"unnamedplus"` | `""` keeps yanks inside lim |
| `colorscheme` | unset | A theme name; unset uses the last one picked (nvchad at first) |
| `icons` · `iconset` | `true` · `"nerd"` | Glyphs at all · `"nerd"`, `"unicode"`, `"emoji"` or `"ascii"` |
| `icons` · `iconset` | `true` · `"nerd"` | Glyphs at all · `"nerd"`, `"unicode"`, `"emoji"` or `"ascii"` (`"unicode"` when no Nerd Font is installed) |
| `statusline_style` | `"rounded"` | Statusline section ends: `"rounded"`, `"slanted"`, `"arrow"` or `"flat"` (Nerd Font icon set only) |
| `splash` | `"random"` | Start screen hydra: `"random"` (one that fits, each start), `"braille"` or `"small"` |
| `mouse` | `true` | Click (text, tabs, tabline buttons), drag to select, wheel; `false` gives the terminal its own selection back (to copy) |
Expand Down
3 changes: 2 additions & 1 deletion docs/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ top of any theme.
## Icon sets

`nerd` (default, Nerd Font devicons as AstroNvim), `unicode` (any font), `emoji` (color emoji,
two cells each) and `ascii`. Switch in the settings (`<Leader>,`), `<Leader>fI`, or the `iconset` option.
two cells each) and `ascii`. When no Nerd Font file is found in the system or user font folders, lim starts with
`unicode` instead; an `iconset` in the config always wins. Switch in the settings (`<Leader>,`), `<Leader>fI`, or the `iconset` option.

| | |
|---|---|
Expand Down
52 changes: 52 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,44 @@ fn user_dir(xdg: &str, windows: &str, home: &[&str]) -> Option<PathBuf> {
Some(base.join("lim"))
}

/// Whether a Nerd Font is installed in the system or user font folders.
/// shortcut: looks at font file names, not the terminal's font; set `iconset` when the guess is wrong.
fn nerd_font_installed() -> bool {
let var = |name: &str, parts: &[&str]| {
env::var_os(name).map(|v| parts.iter().fold(PathBuf::from(v), |p, part| p.join(part)))
};
let dirs: [Option<PathBuf>; 3] = if cfg!(windows) {
[var("WINDIR", &["Fonts"]), var("LOCALAPPDATA", &["Microsoft", "Windows", "Fonts"]), None]
} else if cfg!(target_os = "macos") {
[Some(PathBuf::from("/Library/Fonts")), var("HOME", &["Library", "Fonts"]), None]
} else {
[Some(PathBuf::from("/usr/share/fonts")), var("HOME", &[".local", "share", "fonts"]), var("HOME", &[".fonts"])]
};
dirs.iter().flatten().any(|dir| has_nerd_font(dir, 3))
}

/// A font file named like a Nerd Font (`JetBrainsMonoNerdFont-Regular.ttf`, `CascadiaCodeNF.ttf`) under `dir`.
fn has_nerd_font(dir: &Path, depth: usize) -> bool {
let Ok(entries): io::Result<fs::ReadDir> = fs::read_dir(dir) else { return false };
entries.flatten().any(|entry| {
if entry.file_type().is_ok_and(|t| t.is_dir()) {
return depth > 0 && has_nerd_font(&entry.path(), depth - 1);
}
let name: String = entry.file_name().to_string_lossy().to_lowercase().replace(' ', "");
let Some((stem, ext)): Option<(&str, &str)> = name.rsplit_once('.') else { return false };
let family: &str = stem.split('-').next().unwrap_or(stem);
matches!(ext, "ttf" | "otf" | "ttc")
&& (family.contains("nerdfont") || ["nf", "nfm", "nfp"].iter().any(|s| family.ends_with(s)))
})
}

/// The config and every problem found. A missing file is not a problem.
pub fn load() -> (Config, Vec<String>) {
let mut config: Config = Config::default();
// Nerd Font glyphs show as boxes without the font (a fresh install); the config's `iconset` still wins.
if !nerd_font_installed() {
config.options.iconset = "unicode".into();
}
let mut warnings: Vec<String> = Vec::new();
let Some(path): Option<PathBuf> = path() else { return (config, warnings) };
match fs::read_to_string(&path) {
Expand Down Expand Up @@ -784,6 +819,23 @@ mod tests {
assert_eq!(config.options, Options::default());
}

#[test]
fn finds_nerd_fonts_by_file_name() {
let dir: PathBuf = env::temp_dir().join(format!("lim-fonts-{}", std::process::id()));
let nested: PathBuf = dir.join("truetype").join("jetbrains");
fs::create_dir_all(&nested).unwrap();
fs::write(dir.join("DejaVuSans.ttf"), "").unwrap();
fs::write(dir.join("fonts.conf"), "").unwrap();
assert!(!has_nerd_font(&dir, 3));
for name in ["JetBrainsMonoNerdFont-Regular.ttf", "Hack Nerd Font Complete.otf", "CascadiaCodeNF.ttf"] {
fs::write(nested.join(name), "").unwrap();
assert!(has_nerd_font(&dir, 3), "{name}");
assert!(!has_nerd_font(&dir, 0), "{name} is two folders down");
fs::remove_file(nested.join(name)).unwrap();
}
fs::remove_dir_all(&dir).unwrap();
}

/// Every option written by `save_options` reads back to the same value, and other sections survive.
#[test]
fn saved_options_read_back() {
Expand Down
Loading