use std::env;
#[derive(Clone, Copy, Debug)]
enum IconMode {
Unicode,
Narrow,
Ascii,
}
pub struct Icons {
mode: IconMode,
}
#[derive(Clone, Copy)]
pub struct IconGlyph {
unicode: &'static str,
narrow: &'static str,
ascii: &'static str,
}
pub struct SafeIcons;
impl SafeIcons {
pub const CHECK: IconGlyph = IconGlyph {
unicode: "\u{2714}",
narrow: "\u{2713}",
ascii: "+",
};
pub const CROSS: IconGlyph = IconGlyph {
unicode: "\u{2716}",
narrow: "\u{00D7}",
ascii: "x",
};
pub const LIGHTNING: IconGlyph = IconGlyph {
unicode: "\u{26A1}",
narrow: "*",
ascii: "*",
};
pub const SEARCH: IconGlyph = IconGlyph {
unicode: "\u{1F50D}",
narrow: "?",
ascii: "?",
};
pub const WARNING: IconGlyph = IconGlyph {
unicode: "\u{26A0}",
narrow: "!",
ascii: "!",
};
pub const FIRE: IconGlyph = IconGlyph {
unicode: "\u{1F525}",
narrow: "!",
ascii: "!",
};
pub const FOLDER: IconGlyph = IconGlyph {
unicode: "\u{1F4C2}",
narrow: "[D]",
ascii: "[D]",
};
pub const FILE: IconGlyph = IconGlyph {
unicode: "\u{1F4C4}",
narrow: "[F]",
ascii: "[F]",
};
pub const LINK: IconGlyph = IconGlyph {
unicode: "\u{1F517}",
narrow: "->",
ascii: "->",
};
}
impl Default for Icons {
fn default() -> Self {
Self::new()
}
}
impl Icons {
pub fn new() -> Self {
let mode = Self::detect_mode();
Self { mode }
}
fn detect_mode() -> IconMode {
if is_truthy_env("ZIRO_PLAIN") {
return IconMode::Ascii;
}
if is_truthy_env("ZIRO_ASCII_ICONS") {
return IconMode::Ascii;
}
if is_truthy_env("ZIRO_UNICODE_ICONS") {
return IconMode::Unicode;
}
if is_truthy_env("ZIRO_NARROW") {
return IconMode::Narrow;
}
if is_likely_non_utf8() {
return IconMode::Ascii;
}
if Self::detect_unicode_support() {
IconMode::Unicode
} else {
IconMode::Ascii
}
}
fn detect_unicode_support() -> bool {
if let Ok(locale) = env::var("LC_ALL").or_else(|_| env::var("LANG"))
&& (locale.to_lowercase().contains("utf-8") || locale.contains("65001"))
{
return true;
}
if let Ok(term) = env::var("TERM") {
let term = term.to_lowercase();
if term.contains("xterm")
|| term.contains("screen")
|| term.contains("tmux")
|| term.contains("alacritty")
|| term.contains("kitty")
|| term.contains("iterm")
|| term.contains("gnome")
|| term.contains("konsole")
|| term.contains("rxvt")
|| term.contains("st")
{
return true;
}
if cfg!(target_os = "windows") {
if term.contains("cygwin") || term.contains("msys") || term.contains("mingw") {
return true;
} else if term.contains("win32")
|| term.contains("conhost")
|| term.contains("dumb")
{
return false;
}
}
}
if cfg!(target_os = "windows") {
if let Ok(wt_session) = env::var("WT_SESSION") {
return !wt_session.is_empty();
}
if let Ok(term_program) = env::var("TERM_PROGRAM") {
let term_program = term_program.to_lowercase();
if [
"vscode",
"hyper",
"terminus",
"windowsterminal",
"wt",
"warp",
"warpterminal",
]
.contains(&term_program.as_str())
{
return true;
}
}
if let Ok(shell) = env::var("SHELL") {
if shell.contains("bash") || shell.contains("zsh") || shell.contains("fish") {
return true;
}
}
if let Ok(program_files) = env::var("ProgramFiles") {
let wt_path = std::path::Path::new(&program_files)
.join("WindowsApps")
.join("Microsoft.WindowsTerminal");
if wt_path.exists() {
return true;
}
}
if let Ok(local_app_data) = env::var("LOCALAPPDATA") {
let wt_path = std::path::Path::new(&local_app_data)
.join("Microsoft")
.join("WindowsApps");
if wt_path.exists() && wt_path.join("Microsoft.WindowsTerminal").exists() {
return true;
}
}
if env::var("ConEmuANSI").is_ok() || env::var("ANSICON").is_ok() {
return true;
}
if let Ok(term) = env::var("TERM") {
if !term.is_empty() && !term.contains("win32") && !term.contains("conhost") {
return true;
}
}
}
#[cfg(not(target_os = "windows"))]
{
true
}
#[cfg(target_os = "windows")]
{
env::var("TERM").is_ok() && !env::var("TERM").unwrap_or_default().is_empty()
}
}
pub fn check(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::CHECK, self.mode)
}
pub fn cross(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::CROSS, self.mode)
}
pub fn lightning(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::LIGHTNING, self.mode)
}
pub fn search(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::SEARCH, self.mode)
}
pub fn warning(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::WARNING, self.mode)
}
pub fn fire(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::FIRE, self.mode)
}
pub fn folder(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::FOLDER, self.mode)
}
pub fn file(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::FILE, self.mode)
}
pub fn link(&self) -> StyledEmoji {
StyledEmoji::new(SafeIcons::LINK, self.mode)
}
}
fn is_truthy_env(key: &str) -> bool {
if let Ok(v) = env::var(key) {
let v = v.to_lowercase();
return matches!(v.as_str(), "1" | "true" | "yes" | "on");
}
false
}
fn is_likely_non_utf8() -> bool {
if cfg!(target_os = "windows") {
if env::var("WT_SESSION")
.map(|v| !v.is_empty())
.unwrap_or(false)
{
return false;
}
if let Ok(term_program) = env::var("TERM_PROGRAM") {
let term_program = term_program.to_lowercase();
if [
"vscode",
"hyper",
"terminus",
"windowsterminal",
"wt",
"warp",
"warpterminal",
]
.contains(&term_program.as_str())
{
return false;
}
}
let locale = env::var("LC_ALL")
.or_else(|_| env::var("LANG"))
.unwrap_or_default()
.to_lowercase();
if locale.contains("utf-8") || locale.contains("65001") {
return false;
}
if let Ok(term) = env::var("TERM") {
let term = term.to_lowercase();
if term.contains("xterm")
|| term.contains("screen")
|| term.contains("tmux")
|| term.contains("alacritty")
|| term.contains("kitty")
|| term.contains("iterm")
|| term.contains("gnome")
|| term.contains("konsole")
{
return false;
}
if term.contains("win32") || term.contains("conhost") || term.contains("dumb") {
return true;
}
}
if env::var("ConEmuANSI").is_ok() || env::var("ANSICON").is_ok() {
return false;
}
if let Ok(shell) = env::var("SHELL") {
if shell.contains("bash") || shell.contains("zsh") || shell.contains("fish") {
return false;
}
}
if let Ok(program_files) = env::var("ProgramFiles") {
let wt_path = std::path::Path::new(&program_files)
.join("WindowsApps")
.join("Microsoft.WindowsTerminal");
if wt_path.exists() {
return false;
}
}
return false;
}
let locale = env::var("LC_ALL")
.or_else(|_| env::var("LANG"))
.unwrap_or_default()
.to_lowercase();
if locale.is_empty() {
return false;
}
!locale.contains("utf-8")
}
pub struct StyledEmoji {
glyph: IconGlyph,
mode: IconMode,
}
impl StyledEmoji {
fn new(glyph: IconGlyph, mode: IconMode) -> Self {
Self { glyph, mode }
}
pub fn as_str(&self) -> &str {
match self.mode {
IconMode::Unicode => self.glyph.unicode,
IconMode::Narrow => self.glyph.narrow,
IconMode::Ascii => self.glyph.ascii,
}
}
}
impl std::fmt::Display for StyledEmoji {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub fn icons() -> Icons {
Icons::new()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_icon_creation() {
let icons = Icons::new();
let check = icons.check();
assert!(!check.as_str().is_empty());
}
}