use crate::ported::params::getsparam;
const PADDED_KEYS: [&str; 7] = [
"LEFT_SEGMENT_END_SEPARATOR", "MULTILINE_LAST_PROMPT_PREFIX", "VCS_TAG_ICON", "VCS_BOOKMARK_ICON", "VCS_COMMIT_ICON", "VCS_BRANCH_ICON", "VCS_REMOTE_BRANCH_ICON", ];
fn icon_raw(name: &str) -> Option<&'static str> {
let glyph = match name {
"RULER_CHAR" => "\u{2500}", "LEFT_SEGMENT_SEPARATOR" => "\u{E0B0}", "RIGHT_SEGMENT_SEPARATOR" => "\u{E0B2}", "LEFT_SEGMENT_END_SEPARATOR" => " ", "LEFT_SUBSEGMENT_SEPARATOR" => "\u{E0B1}", "RIGHT_SUBSEGMENT_SEPARATOR" => "\u{E0B3}", "CARRIAGE_RETURN_ICON" => "\u{21B5}", "ROOT_ICON" => "\u{E614}", "SUDO_ICON" => "\u{F09C} ", "RUBY_ICON" => "\u{F219} ", "AWS_ICON" => "\u{F270} ", "AWS_EB_ICON" => "\u{F1BD}", "BACKGROUND_JOBS_ICON" => "\u{F013} ", "TEST_ICON" => "\u{F188} ", "TODO_ICON" => "\u{2611}", "BATTERY_ICON" => "\u{F240} ", "DISK_ICON" => "\u{F0A0} ", "OK_ICON" => "\u{F00C} ", "FAIL_ICON" => "\u{F00D}", "SYMFONY_ICON" => "\u{E757}", "NODE_ICON" => "\u{E617} ", "NODEJS_ICON" => "\u{E617} ", "MULTILINE_FIRST_PROMPT_PREFIX" => "\u{256D}\u{2500}", "MULTILINE_NEWLINE_PROMPT_PREFIX" => "\u{251C}\u{2500}", "MULTILINE_LAST_PROMPT_PREFIX" => "\u{2570}\u{2500} ", "APPLE_ICON" => "\u{F179}", "WINDOWS_ICON" => "\u{F17A} ", "FREEBSD_ICON" => "\u{F30C} ", "ANDROID_ICON" => "\u{F17B}", "LINUX_ARCH_ICON" => "\u{F303}", "LINUX_CENTOS_ICON" => "\u{F304} ", "LINUX_COREOS_ICON" => "\u{F305} ", "LINUX_DEBIAN_ICON" => "\u{F306}", "LINUX_RASPBIAN_ICON" => "\u{F315}", "LINUX_ELEMENTARY_ICON" => "\u{F309} ", "LINUX_FEDORA_ICON" => "\u{F30A} ", "LINUX_GENTOO_ICON" => "\u{F30D} ", "LINUX_MAGEIA_ICON" => "\u{F310}", "LINUX_MINT_ICON" => "\u{F30E} ", "LINUX_NIXOS_ICON" => "\u{F313} ", "LINUX_MANJARO_ICON" => "\u{F312} ", "LINUX_DEVUAN_ICON" => "\u{F307} ", "LINUX_ALPINE_ICON" => "\u{F300} ", "LINUX_AOSC_ICON" => "\u{F301} ", "LINUX_OPENSUSE_ICON" => "\u{F314} ", "LINUX_SABAYON_ICON" => "\u{F317} ", "LINUX_SLACKWARE_ICON" => "\u{F319} ", "LINUX_VOID_ICON" => "\u{F17C}", "LINUX_ARTIX_ICON" => "\u{F17C}", "LINUX_UBUNTU_ICON" => "\u{F31B} ", "LINUX_RHEL_ICON" => "\u{F316} ", "LINUX_AMZN_ICON" => "\u{F270} ", "LINUX_ICON" => "\u{F17C}", "SUNOS_ICON" => "\u{F185} ", "HOME_ICON" => "\u{F015} ", "HOME_SUB_ICON" => "\u{F07C} ", "FOLDER_ICON" => "\u{F115} ", "ETC_ICON" => "\u{F013} ", "NETWORK_ICON" => "\u{F50D} ", "LOAD_ICON" => "\u{F080} ", "SWAP_ICON" => "\u{F464} ", "RAM_ICON" => "\u{F0E4} ", "SERVER_ICON" => "\u{F0AE} ", "VCS_UNTRACKED_ICON" => "\u{F059} ", "VCS_UNSTAGED_ICON" => "\u{F06A} ", "VCS_STAGED_ICON" => "\u{F055} ", "VCS_STASH_ICON" => "\u{F01C} ", "VCS_INCOMING_CHANGES_ICON" => "\u{F01A} ", "VCS_OUTGOING_CHANGES_ICON" => "\u{F01B} ", "VCS_TAG_ICON" => "\u{F02B} ", "VCS_BOOKMARK_ICON" => "\u{F461} ", "VCS_COMMIT_ICON" => "\u{E729} ", "VCS_BRANCH_ICON" => "\u{F126} ", "VCS_REMOTE_BRANCH_ICON" => "\u{E728} ", "VCS_LOADING_ICON" => "", "VCS_GIT_ICON" => "\u{F1D3} ", "VCS_GIT_GITHUB_ICON" => "\u{F113} ", "VCS_GIT_BITBUCKET_ICON" => "\u{E703} ", "VCS_GIT_GITLAB_ICON" => "\u{F296} ", "VCS_HG_ICON" => "\u{F0C3} ", "VCS_SVN_ICON" => "\u{E72D}", "RUST_ICON" => "\u{E7A8}", "PYTHON_ICON" => "\u{E73C} ", "SWIFT_ICON" => "\u{E755}", "GO_ICON" => "\u{E626}", "GOLANG_ICON" => "\u{E626}", "PUBLIC_IP_ICON" => "\u{F0AC} ", "LOCK_ICON" => "\u{F023}", "NORDVPN_ICON" => "\u{F023}", "EXECUTION_TIME_ICON" => "\u{F252} ", "SSH_ICON" => "\u{F489} ", "VPN_ICON" => "\u{F023}", "KUBERNETES_ICON" => "\u{2388}", "DROPBOX_ICON" => "\u{F16B} ", "DATE_ICON" => "\u{F073} ", "TIME_ICON" => "\u{F017} ", "JAVA_ICON" => "\u{E738}", "LARAVEL_ICON" => "\u{E73F}", "RANGER_ICON" => "\u{F00B} ", "MIDNIGHT_COMMANDER_ICON" => "mc", "VIM_ICON" => "\u{E62B}", "TERRAFORM_ICON" => "\u{F1BB} ", "PROXY_ICON" => "\u{2194}", "DOTNET_ICON" => "\u{E77F}", "DOTNET_CORE_ICON" => "\u{E77F}", "AZURE_ICON" => "\u{FD03}", "DIRENV_ICON" => "\u{25BC}", "FLUTTER_ICON" => "F", "GCLOUD_ICON" => "\u{F7B7}", "LUA_ICON" => "\u{E620}", "PERL_ICON" => "\u{E769}", "NNN_ICON" => "nnn", "XPLR_ICON" => "xplr", "TIMEWARRIOR_ICON" => "\u{F49B}", "TASKWARRIOR_ICON" => "\u{F4A0} ", "NIX_SHELL_ICON" => "\u{F313} ", "WIFI_ICON" => "\u{F1EB} ", "ERLANG_ICON" => "\u{E7B1} ", "ELIXIR_ICON" => "\u{E62D}", "POSTGRES_ICON" => "\u{E76E}", "PHP_ICON" => "\u{E608}", "HASKELL_ICON" => "\u{E61F}", "PACKAGE_ICON" => "\u{F8D6}", "JULIA_ICON" => "\u{E624}", "SCALA_ICON" => "\u{E737}", "TOOLBOX_ICON" => "\u{E20F} ", _ => return None,
};
Some(glyph)
}
fn icon_padding_none() -> bool {
getsparam("POWERLEVEL9K_ICON_PADDING").as_deref() == Some("none")
}
pub fn icon(name: &str) -> &'static str {
let raw = match icon_raw(name) {
Some(r) => r,
None => {
tracing::debug!("p10k icons: unknown icon key {name}");
return "";
}
};
if !icon_padding_none() {
if getsparam("POWERLEVEL9K_LEGACY_ICON_SPACING").as_deref() == Some("true") {
tracing::debug!(
"p10k icons: LEGACY_ICON_SPACING=true with ICON_PADDING!=none not ported; using default spacing for {name}"
);
}
return raw; }
if PADDED_KEYS.contains(&name) {
raw
} else {
raw.trim_end_matches(' ')
}
}
pub fn icon_resolved(name: &str) -> String {
if let Some(user) = getsparam(&format!("POWERLEVEL9K_{name}")) {
let decoded = unescape_g(&user);
let mut chars = decoded.chars();
if chars.next() == Some('\u{8}') && chars.next().is_some() && chars.next().is_none() {
return format!("%{{{decoded}%}}");
}
return decoded;
}
icon(name).to_string()
}
fn unescape_g(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut it = s.chars().peekable();
while let Some(c) = it.next() {
if c != '\\' {
out.push(c);
continue;
}
match it.next() {
Some('a') => out.push('\u{7}'),
Some('b') => out.push('\u{8}'),
Some('e') | Some('E') => out.push('\u{1B}'),
Some('f') => out.push('\u{C}'),
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('v') => out.push('\u{B}'),
Some('\\') => out.push('\\'),
Some('x') => push_hex(&mut out, &mut it, 2, "\\x"),
Some('u') => push_hex(&mut out, &mut it, 4, "\\u"),
Some('U') => push_hex(&mut out, &mut it, 8, "\\U"),
Some('0') => {
let mut val: u32 = 0;
let mut n = 0;
while n < 3 {
match it.peek() {
Some(d @ '0'..='7') => {
val = val * 8 + (*d as u32 - '0' as u32);
it.next();
n += 1;
}
_ => break,
}
}
out.push(char::from_u32(val).unwrap_or('\u{0}'));
}
Some(other) => {
out.push('\\');
out.push(other);
}
None => out.push('\\'),
}
}
out
}
fn push_hex(out: &mut String, it: &mut std::iter::Peekable<std::str::Chars<'_>>, max: usize, intro: &str) {
let mut val: u32 = 0;
let mut n = 0;
while n < max {
match it.peek().and_then(|d| d.to_digit(16)) {
Some(d) => {
val = val * 16 + d;
it.next();
n += 1;
}
None => break,
}
}
if n == 0 {
out.push_str(intro);
} else {
match char::from_u32(val) {
Some(ch) => out.push(ch),
None => out.push_str(intro),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn branch_icon_raw_has_single_trailing_space() {
assert_eq!(icon_raw("VCS_BRANCH_ICON"), Some("\u{F126} "));
}
#[test]
fn unknown_key_is_empty() {
assert_eq!(icon("NO_SUCH_ICON_KEY"), "");
}
#[test]
fn unescape_g_handles_unicode_and_octal() {
assert_eq!(unescape_g("\\uE0B0"), "\u{E0B0}");
assert_eq!(unescape_g("\\U0001F331"), "\u{1F331}");
assert_eq!(unescape_g("\\x41"), "A");
assert_eq!(unescape_g("\\0101"), "A");
assert_eq!(unescape_g("plain"), "plain");
assert_eq!(unescape_g("\\q"), "\\q"); }
#[test]
fn padded_keys_end_in_exactly_one_space() {
for key in PADDED_KEYS {
let raw = icon_raw(key).unwrap_or_default();
assert!(raw.ends_with(' '), "{key} missing trailing space");
assert!(!raw.ends_with(" "), "{key} has multiple trailing spaces");
}
}
}