use crate::extensions::p10k::config::{p9k_global, p9k_param};
use crate::extensions::p10k::icons;
use crate::extensions::p10k::render::Segment;
use crate::ported::params::{getaparam, getsparam, setsparam};
use crate::ported::utils::getkeystring;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
pub fn build_segment(name: &str) -> Option<Vec<Segment>> {
match name {
"time" => Some(time_segments()), "ram" => Some(ram_segments()), "load" => Some(load_segments()), "disk_usage" => Some(disk_usage_segments()), "swap" => Some(swap_segments()), "battery" => Some(battery_segments()), "wifi" => Some(wifi_segments()), "command_execution_time" => Some(command_execution_time_segments()), "vi_mode" => Some(vi_mode_segments()), "proxy" => Some(proxy_segments()), "todo" => Some(todo_segments()), "timewarrior" => Some(timewarrior_segments()), "taskwarrior" => Some(taskwarrior_segments()), "nix_shell" => Some(nix_shell_segments()), "vim_shell" => Some(vim_shell_segments()), "midnight_commander" => Some(midnight_commander_segments()), _ => None,
}
}
fn color1() -> &'static str {
if p9k_global("COLOR_SCHEME", "dark") == "light" {
"7"
} else {
"0"
}
}
fn color2() -> &'static str {
if p9k_global("COLOR_SCHEME", "dark") == "light" {
"0"
} else {
"7"
}
}
fn env_or_param(name: &str) -> String {
if let Some(v) = getsparam(name) {
return v;
}
std::env::var(name).unwrap_or_default()
}
fn global_bool(name: &str, default: bool) -> bool {
match getsparam(&format!("POWERLEVEL9K_{name}")) {
Some(v) => v == "true",
None => default,
}
}
fn global_int(name: &str, default: i64) -> i64 {
getsparam(&format!("POWERLEVEL9K_{name}"))
.and_then(|v| v.trim().parse::<i64>().ok())
.unwrap_or(default)
}
fn global_float(name: &str, default: f64) -> f64 {
getsparam(&format!("POWERLEVEL9K_{name}"))
.and_then(|v| v.trim().parse::<f64>().ok())
.unwrap_or(default)
}
fn esc_pct(s: &str) -> String {
s.replace('%', "%%")
}
fn decode_g(s: &str) -> String {
getkeystring(s).0
}
fn seg_icon(segment: &str, state: Option<&str>, key: &str) -> String {
let probed = p9k_param(segment, state, key, "\u{1}");
if probed == "\u{1}" {
return icons::icon(key).to_string();
}
let decoded = decode_g(&probed);
let mut ch = decoded.chars();
if ch.next() == Some('\u{8}') && ch.next().is_some() && ch.next().is_none() {
return format!("%{{{decoded}%}}");
}
decoded
}
fn apply_visual_identifier(segment: &str, state: Option<&str>, icon: String) -> Option<String> {
let exp = p9k_param(
segment,
state,
"VISUAL_IDENTIFIER_EXPANSION",
"${P9K_VISUAL_IDENTIFIER}",
);
let resolved = if exp == "${P9K_VISUAL_IDENTIFIER}" {
icon
} else if !exp.contains('$') {
exp
} else {
tracing::debug!(
target: "p10k",
segment,
?state,
%exp,
"dynamic VISUAL_IDENTIFIER_EXPANSION unported — using resolved icon"
);
icon
};
if resolved.is_empty() {
None
} else {
Some(resolved)
}
}
fn apply_content_expansion(segment: &str, state: Option<&str>, content: String) -> String {
let exp = p9k_param(segment, state, "CONTENT_EXPANSION", "${P9K_CONTENT}");
if exp == "${P9K_CONTENT}" {
return content;
}
if !exp.contains('$') {
return exp;
}
if exp.contains("${P9K_CONTENT}") {
let out = exp.replace("${P9K_CONTENT}", &content);
if out.contains('$') {
tracing::debug!(
target: "p10k",
segment, ?state, %exp,
"CONTENT_EXPANSION has unevaluated expansions beyond ${{P9K_CONTENT}}"
);
}
return out;
}
tracing::debug!(
target: "p10k",
segment, ?state, %exp,
"dynamic CONTENT_EXPANSION unported — using segment content"
);
content
}
fn make_segment(
name: &str,
state: Option<&str>,
default_bg: &str,
default_fg: &str,
icon_key: &str,
content: String,
) -> Segment {
let bg = p9k_param(name, state, "BACKGROUND", default_bg);
let fg = p9k_param(name, state, "FOREGROUND", default_fg);
let icon_glyph = if icon_key.is_empty() {
String::new()
} else {
seg_icon(name, state, icon_key)
};
let icon = apply_visual_identifier(name, state, icon_glyph);
let content = apply_content_expansion(name, state, content);
Segment {
name: name.to_string(),
state: state.map(|s| s.to_string()),
content,
icon,
fg,
bg,
}
}
fn cmd_on_path(name: &str) -> Option<PathBuf> {
let path_var = env_or_param("PATH");
for dir in path_var.split(':').filter(|d| !d.is_empty()) {
let cand = std::path::Path::new(dir).join(name);
let is_exec = std::fs::metadata(&cand)
.map(|md| {
use std::os::unix::fs::PermissionsExt;
md.is_file() && md.permissions().mode() & 0o111 != 0
})
.unwrap_or(false);
if is_exec {
return Some(cand);
}
}
None
}
static TTL_CACHE: OnceLock<Mutex<HashMap<&'static str, (Instant, Option<String>)>>> =
OnceLock::new();
fn cached_ttl(
key: &'static str,
ttl: Duration,
run: impl FnOnce() -> Option<String>,
) -> Option<String> {
let m = TTL_CACHE.get_or_init(Default::default);
if let Ok(guard) = m.lock() {
if let Some((at, val)) = guard.get(key) {
if at.elapsed() < ttl {
return val.clone();
}
}
}
let val = run();
if let Ok(mut guard) = m.lock() {
guard.insert(key, (Instant::now(), val.clone()));
}
val
}
fn run_tool(bin: &std::path::Path, args: &[&str]) -> Option<String> {
match std::process::Command::new(bin)
.args(args)
.stdin(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.output()
{
Ok(out) if out.status.success() => Some(String::from_utf8_lossy(&out.stdout).into_owned()),
Ok(_) => None,
Err(e) => {
tracing::debug!(target: "p10k", bin = %bin.display(), %e, "tool spawn failed");
None
}
}
}
fn human_readable_bytes(bytes: f64) -> String {
const SUFFIX: [&str; 9] = ["B", "K", "M", "G", "T", "P", "E", "Z", "Y"];
let mut n = bytes;
let mut suf = SUFFIX[0];
for s in SUFFIX {
suf = s;
if n < 1024.0 {
break;
}
n /= 1024.0; }
let mut ret = if n >= 100.0 {
format!("{n:.0}.") } else if n >= 10.0 {
format!("{n:.1}") } else {
format!("{n:.2}") };
while ret.ends_with('0') {
ret.pop();
}
if ret.ends_with('.') {
ret.pop();
}
format!("{ret}{suf}")
}
fn human_readable_duration(seconds: f64, precision: usize, format: &str) -> String {
if seconds < 60.0 {
if precision == 0 {
let sec = (seconds + 0.5) as i64;
format!("{sec}s")
} else {
format!("{seconds:.precision$}s")
}
} else {
let d = (seconds + 0.5) as i64;
if format == "H:M:S" {
let mut text = format!("{:02}", d % 60); if d >= 60 {
text = format!("{:02}:{text}", d / 60 % 60); if d >= 36000 {
text = format!("{}:{text}", d / 3600); } else if d >= 3600 {
text = format!("0{}:{text}", d / 3600); }
}
text
} else {
let mut text = format!("{}s", d % 60); if d >= 60 {
text = format!("{}m {text}", d / 60 % 60); if d >= 3600 {
text = format!("{}h {text}", d / 3600 % 24); if d >= 86400 {
text = format!("{}d {text}", d / 86400); }
}
}
text
}
}
}
fn time_segments() -> Vec<Segment> {
let fmt = decode_g(&p9k_global("TIME_FORMAT", "%D{%H:%M:%S}"));
if global_bool("TIME_UPDATE_ON_COMMAND", false) {
tracing::debug!(target: "p10k", "TIME_UPDATE_ON_COMMAND unported — static time per prompt");
}
vec![make_segment("time", None, color2(), color1(), "TIME_ICON", fmt)]
}
fn vi_mode_segments() -> Vec<Segment> {
let keymap = crate::ported::zle::zle_keymap::curkeymapname().clone();
let insert_str = decode_g(
&getsparam("POWERLEVEL9K_VI_INSERT_MODE_STRING").unwrap_or_else(|| "INSERT".into()),
);
let command_str = decode_g(
&getsparam("POWERLEVEL9K_VI_COMMAND_MODE_STRING").unwrap_or_else(|| "NORMAL".into()),
);
let visual_str = getsparam("POWERLEVEL9K_VI_VISUAL_MODE_STRING");
let overwrite_str = getsparam("POWERLEVEL9K_VI_OVERWRITE_MODE_STRING");
if keymap == "vicmd" {
if let Some(v) = &visual_str {
if crate::ported::zle::zle_params::get_region_active() != 0 {
return vec![make_segment(
"vi_mode",
Some("VISUAL"),
color1(),
"white",
"",
decode_g(v),
)];
}
}
vec![make_segment(
"vi_mode",
Some("NORMAL"),
color1(),
"white",
"",
command_str,
)]
} else {
if let Some(o) = &overwrite_str {
if crate::ported::zle::zle_main::INSMODE.load(Ordering::Relaxed) == 0 {
return vec![make_segment(
"vi_mode",
Some("OVERWRITE"),
color1(),
"blue",
"",
decode_g(o),
)];
}
}
if insert_str.is_empty() {
return vec![];
}
vec![make_segment(
"vi_mode",
Some("INSERT"),
color1(),
"blue",
"",
insert_str,
)]
}
}
fn command_execution_time_segments() -> Vec<Segment> {
let Some(dur) = crate::p10k::last_exec_duration() else {
return vec![];
};
let secs = dur.as_secs_f64();
if secs < global_float("COMMAND_EXECUTION_TIME_THRESHOLD", 3.0) {
return vec![];
}
let precision = global_int("COMMAND_EXECUTION_TIME_PRECISION", 2).max(0) as usize;
let fmt = p9k_global("COMMAND_EXECUTION_TIME_FORMAT", "H:M:S");
let text = human_readable_duration(secs, precision, &fmt);
vec![make_segment(
"command_execution_time",
None,
"red",
"yellow1",
"EXECUTION_TIME_ICON",
text,
)]
}
fn num_cpus() -> f64 {
std::thread::available_parallelism()
.map(|n| n.get() as f64)
.unwrap_or(1.0) }
fn load_segments() -> Vec<Segment> {
let mut avg = [0f64; 3];
if unsafe { libc::getloadavg(avg.as_mut_ptr(), 3) } != 3 {
tracing::warn!(target: "p10k", "getloadavg failed — load segment hidden");
return vec![];
}
let idx = match global_int("LOAD_WHICH", 5) {
1 => 0,
15 => 2,
_ => 1,
};
let load = avg[idx];
let pct = 100.0 * load / num_cpus();
let (state, bg) = if pct > global_float("LOAD_CRITICAL_PCT", 70.0) {
("CRITICAL", "red") } else if pct > global_float("LOAD_WARNING_PCT", 50.0) {
("WARNING", "yellow") } else {
("NORMAL", "green") };
vec![make_segment(
"load",
Some(state),
bg,
color1(),
"LOAD_ICON",
format!("{load:.2}"),
)]
}
#[cfg(target_os = "macos")]
fn ram_free_bytes() -> Option<f64> {
let out = cached_ttl("ram.vm_stat", Duration::from_secs(10), || {
run_tool(&cmd_on_path("vm_stat")?, &[])
})?;
fn pages(out: &str, key: &str) -> Option<f64> {
let rest = out.lines().find_map(|l| l.strip_prefix(key))?;
rest.trim().trim_end_matches('.').parse::<f64>().ok()
}
let free = pages(&out, "Pages free:")? + pages(&out, "Pages inactive:")?;
let pagesize = match unsafe { libc::sysconf(libc::_SC_PAGESIZE) } {
n if n > 0 => n as f64,
_ => 4096.0,
};
Some(free * pagesize)
}
#[cfg(not(target_os = "macos"))]
fn ram_free_bytes() -> Option<f64> {
let stat = std::fs::read_to_string("/proc/meminfo").ok()?;
let avail = meminfo_kb(&stat, "MemAvailable:").or_else(|| meminfo_kb(&stat, "MemFree:"))?;
Some(avail * 1024.0) }
#[cfg(not(target_os = "macos"))]
fn meminfo_kb(stat: &str, key: &str) -> Option<f64> {
let rest = stat.lines().find_map(|l| l.strip_prefix(key))?;
rest.trim()
.trim_end_matches("kB")
.trim()
.parse::<f64>()
.ok()
}
fn ram_segments() -> Vec<Segment> {
let Some(free) = ram_free_bytes() else {
return vec![]; };
vec![make_segment(
"ram",
None,
"yellow",
color1(),
"RAM_ICON",
human_readable_bytes(free),
)]
}
#[cfg(target_os = "macos")]
fn swap_used_bytes() -> Option<f64> {
let name = std::ffi::CString::new("vm.swapusage").ok()?;
let mut xsw: libc::xsw_usage = unsafe { std::mem::zeroed() };
let mut len = std::mem::size_of::<libc::xsw_usage>();
let rc = unsafe {
libc::sysctlbyname(
name.as_ptr(),
&mut xsw as *mut _ as *mut libc::c_void,
&mut len,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return None;
}
Some(xsw.xsu_used as f64)
}
#[cfg(not(target_os = "macos"))]
fn swap_used_bytes() -> Option<f64> {
let stat = std::fs::read_to_string("/proc/meminfo").ok()?;
let total = meminfo_kb(&stat, "SwapTotal:")?;
let free = meminfo_kb(&stat, "SwapFree:")?;
Some((total - free) * 1024.0)
}
fn swap_segments() -> Vec<Segment> {
let Some(used) = swap_used_bytes() else {
return vec![]; };
vec![make_segment(
"swap",
None,
"yellow",
color1(),
"SWAP_ICON",
human_readable_bytes(used),
)]
}
fn disk_usage_segments() -> Vec<Segment> {
let cwd = env_or_param("PWD");
let cwd = if cwd.is_empty() { ".".to_string() } else { cwd };
let Ok(c) = std::ffi::CString::new(cwd) else {
return vec![];
};
let mut vfs: libc::statvfs = unsafe { std::mem::zeroed() };
if unsafe { libc::statvfs(c.as_ptr(), &mut vfs) } != 0 {
return vec![]; }
let used = (vfs.f_blocks as u64).saturating_sub(vfs.f_bfree as u64);
let total = used + vfs.f_bavail as u64;
if total == 0 {
return vec![];
}
let pct = (used * 100).div_ceil(total) as i64; let (state, bg, fg): (&str, &str, &str) = if pct >= global_int("DISK_USAGE_CRITICAL_LEVEL", 95)
{
("CRITICAL", "red", "white") } else if pct >= global_int("DISK_USAGE_WARNING_LEVEL", 90) {
("WARNING", "yellow", color1()) } else if !global_bool("DISK_USAGE_ONLY_WARNING", false) {
("NORMAL", color1(), "yellow") } else {
return vec![]; };
vec![make_segment(
"disk_usage",
Some(state),
bg,
fg,
"DISK_ICON",
format!("{pct}%%"), )]
}
fn battery_arr(state: &str, key: &str, split_scalar: bool) -> Vec<String> {
for name in [
format!("POWERLEVEL9K_BATTERY_{state}_{key}"),
format!("POWERLEVEL9K_BATTERY_{key}"),
] {
if let Some(v) = getaparam(&name) {
return v.iter().map(|s| decode_g(s)).collect(); }
if let Some(s) = getsparam(&name) {
if split_scalar {
return decode_g(&s).chars().map(String::from).collect();
}
return vec![decode_g(&s)];
}
}
Vec::new()
}
fn battery_level_pick(arr: &[String], pct: i64) -> Option<String> {
if arr.is_empty() {
return None;
}
let mut idx = arr.len() as i64;
if pct < 100 {
idx = pct * arr.len() as i64 / 100 + 1;
}
arr.get((idx - 1).max(0) as usize).cloned()
}
#[cfg(target_os = "macos")]
fn battery_status() -> Option<(&'static str, i64, String)> {
let raw = cached_ttl("battery.pmset", Duration::from_secs(30), || {
let out = run_tool(&cmd_on_path("pmset")?, &["-g", "batt"])?;
out.lines().nth(1).map(str::to_string)
})?;
if !raw.contains("InternalBattery") {
return None; }
let fields: Vec<&str> = raw.split("; ").collect();
let mut remain = fields
.get(2)
.and_then(|f| f.split_whitespace().next())
.unwrap_or("")
.to_string();
if remain.contains("no") {
remain = "...".to_string();
}
let pos = raw.find('%')?;
let digits: &str = &raw[..pos];
let start = digits
.rfind(|c: char| !c.is_ascii_digit())
.map(|i| i + digits[i..].chars().next().map_or(1, |c| c.len_utf8()))
.unwrap_or(0);
let bat_percent: i64 = digits[start..].parse().ok()?;
let state = match fields.get(1).copied().unwrap_or("") {
"charging" | "finishing charge" | "AC attached" => {
if bat_percent == 100 {
remain.clear(); "CHARGED"
} else {
"CHARGING" }
}
"discharging" => {
if bat_percent < global_int("BATTERY_LOW_THRESHOLD", 10) {
"LOW"
} else {
"DISCONNECTED"
}
}
_ => {
remain.clear(); "CHARGED"
}
};
Some((state, bat_percent, remain))
}
#[cfg(not(target_os = "macos"))]
fn read_file_first(dir: &std::path::Path, names: &[&str]) -> Option<String> {
let path = names.iter().map(|n| dir.join(n)).find(|p| p.exists())?;
let content = std::fs::read_to_string(path).ok()?;
let line = content.lines().next().unwrap_or("").to_string();
(!line.is_empty()).then_some(line)
}
#[cfg(not(target_os = "macos"))]
fn read_file_int(dir: &std::path::Path, names: &[&str]) -> Option<i64> {
read_file_first(dir, names)?.trim().parse::<i64>().ok()
}
#[cfg(not(target_os = "macos"))]
fn battery_status() -> Option<(&'static str, i64, String)> {
let rd = std::fs::read_dir("/sys/class/power_supply").ok()?;
let mut bats: Vec<PathBuf> = rd
.flatten()
.filter(|e| {
let n = e.file_name();
let n = n.to_string_lossy();
n.starts_with("CMB") || n.starts_with("BAT") || n == "battery"
})
.map(|e| e.path())
.filter(|p| p.is_dir())
.collect();
bats.sort();
if bats.is_empty() {
return None;
}
let mut energy_now: i64 = 0;
let mut energy_full: i64 = 0;
let mut power_now: i64 = 0;
let mut is_full = true;
let mut is_calculating = false;
let mut is_charching = false;
for dir in &bats {
let mut pow: i64 = 0;
let mut full: i64 = 0;
if let Some(v) = read_file_int(dir, &["charge_counter", "charge_full", "energy_full"]) {
full = v;
energy_full += v; }
if let Some(s) = read_file_first(dir, &["current_now", "power_now"]) {
if s.len() < 9 {
if let Ok(v) = s.trim().parse::<i64>() {
pow = v;
power_now += v; }
}
}
if let Some(cap) = read_file_int(dir, &["capacity"]) {
energy_now = (energy_now as f64 + cap as f64 * full as f64 / 100.0 + 0.5) as i64;
} else if let Some(v) = read_file_int(dir, &["charge_now", "energy_now"]) {
energy_now += v; }
let Some(bat_status) = read_file_first(dir, &["status"]) else {
continue;
};
if bat_status != "Full" {
is_full = false; }
if bat_status == "Charging" {
is_charching = true; }
if (bat_status == "Charging" || bat_status == "Discharging") && pow == 0 {
is_calculating = true; }
}
if energy_full == 0 {
return None;
}
let mut bat_percent = (100.0 * energy_now as f64 / energy_full as f64 + 0.5) as i64;
if bat_percent > 100 {
bat_percent = 100;
}
let mut remain = String::new();
let state: &'static str;
if is_full || (bat_percent == 100 && is_charching) {
state = "CHARGED"; } else {
state = if is_charching {
"CHARGING" } else if bat_percent < global_int("BATTERY_LOW_THRESHOLD", 10) {
"LOW" } else {
"DISCONNECTED" };
if power_now > 0 {
let e = if is_charching {
energy_full - energy_now
} else {
energy_now
};
let minutes = 60 * e / power_now; if minutes > 0 {
remain = format!("{}:{:02}", minutes / 60, minutes % 60);
}
} else if is_calculating {
remain = "...".to_string(); }
}
Some((state, bat_percent, remain))
}
fn battery_segments() -> Vec<Segment> {
let Some((state, bat_percent, remain)) = battery_status() else {
return vec![];
};
let base_hide = global_int("BATTERY_HIDE_ABOVE_THRESHOLD", 999);
if bat_percent >= global_int(&format!("BATTERY_{state}_HIDE_ABOVE_THRESHOLD"), base_hide) {
return vec![];
}
let mut msg = format!("{bat_percent}%%");
if global_bool("BATTERY_VERBOSE", true) && !remain.is_empty() {
msg.push_str(&format!(" ({remain})"));
}
let stage = battery_level_pick(&battery_arr(state, "STAGES", true), bat_percent);
let icon_glyph = match &stage {
Some(g) => g.clone(),
None => seg_icon("battery", Some(state), "BATTERY_ICON"),
};
let default_bg =
battery_level_pick(&battery_arr(state, "LEVEL_BACKGROUND", false), bat_percent)
.unwrap_or_else(|| color1().to_string());
let state_fg = match state {
"LOW" => "red",
"CHARGING" => "yellow",
"CHARGED" => "green",
_ => color2(), };
let default_fg =
battery_level_pick(&battery_arr(state, "LEVEL_FOREGROUND", false), bat_percent)
.unwrap_or_else(|| state_fg.to_string());
let bg = p9k_param("battery", Some(state), "BACKGROUND", &default_bg);
let fg = p9k_param("battery", Some(state), "FOREGROUND", &default_fg);
let icon = apply_visual_identifier("battery", Some(state), icon_glyph);
let content = apply_content_expansion("battery", Some(state), msg);
vec![Segment {
name: "battery".to_string(),
state: Some(state.to_string()),
content,
icon,
fg,
bg,
}]
}
#[cfg_attr(target_os = "macos", allow(dead_code))]
fn parse_proc_wireless(content: &str) -> Option<(String, String, String)> {
let lines: Vec<&str> = content.lines().filter(|l| !l.contains('|')).collect();
if lines.len() != 1 {
return None;
}
let parts: Vec<&str> = lines[0].split_whitespace().collect();
let strip_dot = |s: &str| match s.rfind('.') {
Some(i) => s[..i].to_string(),
None => s.to_string(),
};
let raw_iface = parts.first()?;
let iface = raw_iface.strip_suffix(':').unwrap_or(raw_iface); let state = parts.get(1)?;
let rssi = strip_dot(parts.get(3)?);
let noise = strip_dot(parts.get(4)?);
let zero_or_neg = |s: &str| {
s == "0"
|| (s.len() > 1 && s.starts_with('-') && s[1..].bytes().all(|b| b.is_ascii_digit()))
};
if iface.is_empty()
|| state.is_empty()
|| !state.bytes().all(|b| b == b'0')
|| !zero_or_neg(&rssi)
|| !zero_or_neg(&noise)
{
return None;
}
Some((iface.to_string(), rssi, noise))
}
#[cfg_attr(target_os = "macos", allow(dead_code))]
fn parse_iw_link(out: &str) -> Option<(String, String)> {
let mut ssid = String::new();
let mut last_tx_rate = String::new();
for line in out.lines() {
let t = line.trim_start();
if let Some(rest) = t.strip_prefix("SSID:") {
if rest.starts_with(char::is_whitespace) {
ssid = rest.trim_start().to_string();
}
} else if let Some(rest) = t.strip_prefix("tx bitrate:") {
let rest = rest.trim_start();
if let Some(tok) = rest.split_whitespace().next() {
if rest[tok.len()..].starts_with(" MBit/s") {
last_tx_rate = tok.to_string();
if let Some((a, b)) = last_tx_rate.split_once('.') {
let digits =
|s: &str| !s.is_empty() && s.bytes().all(|c: u8| c.is_ascii_digit());
if digits(a) && digits(b) {
while last_tx_rate.ends_with('0') {
last_tx_rate.pop();
}
if last_tx_rate.ends_with('.') {
last_tx_rate.pop();
}
}
}
}
}
}
}
if ssid.is_empty() || last_tx_rate.is_empty() {
return None;
}
Some((ssid, last_tx_rate))
}
#[cfg_attr(target_os = "macos", allow(dead_code))]
fn wifi_bars(snr_margin: i64) -> i64 {
if snr_margin >= 40 {
4
} else if snr_margin >= 25 {
3
} else if snr_margin >= 15 {
2
} else if snr_margin >= 10 {
1
} else {
0
}
}
#[cfg(target_os = "macos")]
fn wifi_status() -> Option<(String, String, String, String, String)> {
tracing::debug!(target: "p10k", "wifi: no viable macOS data source (airport CLI removed) — hidden");
None
}
#[cfg(not(target_os = "macos"))]
fn wifi_status() -> Option<(String, String, String, String, String)> {
let joined = cached_ttl("wifi.iw", Duration::from_secs(10), || {
let iw = cmd_on_path("iw")?;
let content = std::fs::read_to_string("/proc/net/wireless").ok()?;
let (iface, rssi, noise) = parse_proc_wireless(&content)?;
let out = run_tool(&iw, &["dev", &iface, "link"])?;
let (ssid, last_tx_rate) = parse_iw_link(&out)?;
let snr = rssi.parse::<i64>().ok()? - noise.parse::<i64>().ok()?;
let bars = wifi_bars(snr);
Some(format!(
"{ssid}\u{1f}{last_tx_rate}\u{1f}{rssi}\u{1f}{noise}\u{1f}{bars}"
))
})?;
let mut f = joined.split('\u{1f}').map(str::to_string);
Some((f.next()?, f.next()?, f.next()?, f.next()?, f.next()?))
}
fn wifi_segments() -> Vec<Segment> {
let Some((ssid, last_tx_rate, rssi, noise, bars)) = wifi_status() else {
return vec![]; };
let _ = setsparam("P9K_WIFI_SSID", &ssid);
let _ = setsparam("P9K_WIFI_LAST_TX_RATE", &last_tx_rate);
let _ = setsparam("P9K_WIFI_LINK_AUTH", "");
let _ = setsparam("P9K_WIFI_RSSI", &rssi);
let _ = setsparam("P9K_WIFI_NOISE", &noise);
let _ = setsparam("P9K_WIFI_BARS", &bars);
vec![make_segment(
"wifi",
None,
"green",
color1(),
"WIFI_ICON",
format!("{last_tx_rate} Mbps"),
)]
}
fn proxy_strip(v: &str) -> String {
let v = v.split_once("://").map_or(v, |(_, r)| r); let v = v.rsplit_once('@').map_or(v, |(_, r)| r); v.split('/').next().unwrap_or(v).to_string() }
fn proxy_segments() -> Vec<Segment> {
const VARS: [&str; 8] = [
"all_proxy",
"http_proxy",
"https_proxy",
"ftp_proxy",
"ALL_PROXY",
"HTTP_PROXY",
"HTTPS_PROXY",
"FTP_PROXY",
];
let mut hosts: Vec<String> = Vec::new(); for var in VARS {
let v = env_or_param(var);
if v.is_empty() {
continue;
}
let host = proxy_strip(&v);
if !hosts.contains(&host) {
hosts.push(host);
}
}
if hosts.is_empty() {
return vec![]; }
let content = if hosts.len() == 1 {
esc_pct(&hosts[0])
} else {
String::new()
};
vec![make_segment(
"proxy",
None,
color1(),
"blue",
"PROXY_ICON",
content,
)]
}
fn todo_segments() -> Vec<Segment> {
let Some(bin) = cmd_on_path("todo.sh").or_else(|| cmd_on_path("todo-txt")) else {
return vec![]; };
let Some(last) = cached_ttl("todo.count", Duration::from_secs(30), || {
let out = run_tool(&bin, &["-p", "ls"])?;
out.lines().last().map(str::to_string)
}) else {
return vec![];
};
let Some(rest) = last.strip_prefix("TODO: ") else {
return vec![];
};
let mut it = rest.split_whitespace();
let (Some(filtered), Some("of"), Some(total)) = (it.next(), it.next(), it.next()) else {
return vec![];
};
let (Ok(filtered), Ok(total)) = (filtered.parse::<i64>(), total.parse::<i64>()) else {
return vec![];
};
if (total == 0 && global_bool("TODO_HIDE_ZERO_TOTAL", false))
|| (filtered == 0 && global_bool("TODO_HIDE_ZERO_FILTERED", false))
{
return vec![];
}
let text = if total == filtered {
total.to_string()
} else {
format!("{filtered}/{total}")
};
vec![make_segment(
"todo",
None,
"grey50",
color1(),
"TODO_ICON",
text,
)]
}
fn timewarrior_segments() -> Vec<Segment> {
timewarrior_inner().unwrap_or_default()
}
fn timewarrior_inner() -> Option<Vec<Segment>> {
cmd_on_path("timew")?;
let db = env_or_param("TIMEWARRIORDB");
let dir = if db.is_empty() {
PathBuf::from(env_or_param("HOME")).join(".timewarrior")
} else {
PathBuf::from(db)
}
.join("data");
let rd = std::fs::read_dir(&dir).ok()?; let is_data_name = |n: &str| {
n.strip_suffix(".data").is_some_and(|stem| {
stem.split_once('-').is_some_and(|(a, b)| {
!a.is_empty()
&& !b.is_empty()
&& a.bytes().all(|c| c.is_ascii_digit())
&& b.bytes().all(|c| c.is_ascii_digit())
})
})
};
let newest = rd
.flatten()
.filter_map(|e| {
let name = e.file_name().to_string_lossy().into_owned();
is_data_name(&name).then_some(name)
})
.max()?;
let content = std::fs::read_to_string(dir.join(&newest)).ok()?;
let last = content.lines().rev().find(|l| !l.is_empty())?;
let rest = last.strip_prefix("inc ")?;
let (first, tail) = match rest.split_once(' ') {
Some((f, t)) => (f, Some(t)),
None => (rest, None),
};
if first.is_empty() {
return None;
}
let tags = match tail {
None => String::new(),
Some(t) => {
let t = t.trim_start_matches(' ');
t.strip_prefix('#')?.trim_matches(' ').to_string()
}
};
Some(vec![make_segment(
"timewarrior",
None,
"grey",
"255",
"TIMEWARRIOR_ICON",
esc_pct(&tags),
)])
}
fn taskwarrior_segments() -> Vec<Segment> {
let Some(bin) = cmd_on_path("task") else {
return vec![];
};
let text = cached_ttl("taskwarrior.counts", Duration::from_secs(30), || {
let count = |tag: &str| -> Option<i64> {
let out = run_tool(
&bin,
&[
&format!("+{tag}"),
"count",
"rc.color=0",
"rc._forcecolor=0",
],
)?;
let n: i64 = out.trim().parse().ok()?;
(n >= 1).then_some(n) };
let mut text = String::new();
if let Some(o) = count("OVERDUE") {
text.push_str(&format!("!{o}"));
}
if let Some(p) = count("PENDING") {
if !text.is_empty() {
text.push('/');
}
text.push_str(&p.to_string());
}
Some(text)
})
.unwrap_or_default();
if text.is_empty() {
return vec![];
}
vec![make_segment(
"taskwarrior",
None,
"6",
color1(),
"TASKWARRIOR_ICON",
text,
)]
}
fn nix_shell_segments() -> Vec<Segment> {
let v = env_or_param("IN_NIX_SHELL");
if v.is_empty() || v == "0" {
return vec![];
}
let content = if v == "pure" || v == "impure" {
v
} else {
String::new()
};
vec![make_segment(
"nix_shell",
None,
"4",
color1(),
"NIX_SHELL_ICON",
content,
)]
}
fn vim_shell_segments() -> Vec<Segment> {
if env_or_param("VIMRUNTIME").is_empty() {
return vec![];
}
vec![make_segment(
"vim_shell",
None,
"green",
color1(),
"VIM_ICON",
String::new(),
)]
}
fn midnight_commander_segments() -> Vec<Segment> {
if env_or_param("MC_TMPDIR").is_empty() {
return vec![];
}
vec![make_segment(
"midnight_commander",
None,
color1(),
"yellow",
"MIDNIGHT_COMMANDER_ICON",
String::new(),
)]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn human_readable_bytes_literals() {
assert_eq!(human_readable_bytes(0.0), "0B");
assert_eq!(human_readable_bytes(512.0), "512B"); assert_eq!(human_readable_bytes(1023.0), "1023B"); assert_eq!(human_readable_bytes(1536.0), "1.5K"); assert_eq!(human_readable_bytes(10.0 * 1024.0), "10K"); assert_eq!(human_readable_bytes(16000.0), "15.6K"); assert_eq!(human_readable_bytes(2.0 * 1024.0 * 1024.0), "2M"); assert_eq!(
human_readable_bytes(100.0 * 1024.0 * 1024.0 * 1024.0),
"100G" );
assert_eq!(human_readable_bytes(1024f64.powi(4) * 1.25), "1.25T");
}
#[test]
fn human_readable_duration_sub_minute() {
assert_eq!(human_readable_duration(3.0, 0, "H:M:S"), "3s");
assert_eq!(human_readable_duration(3.6, 0, "H:M:S"), "4s"); assert_eq!(human_readable_duration(3.4, 0, "d h m s"), "3s"); assert_eq!(human_readable_duration(3.5, 2, "H:M:S"), "3.50s"); assert_eq!(human_readable_duration(59.0, 1, "d h m s"), "59.0s");
}
#[test]
fn human_readable_duration_hms() {
assert_eq!(human_readable_duration(95.4, 0, "H:M:S"), "01:35"); assert_eq!(human_readable_duration(3661.0, 0, "H:M:S"), "01:01:01");
assert_eq!(human_readable_duration(36000.0, 0, "H:M:S"), "10:00:00"); assert_eq!(human_readable_duration(90061.0, 0, "H:M:S"), "25:01:01"); }
#[test]
fn human_readable_duration_dhms() {
assert_eq!(human_readable_duration(125.0, 0, "d h m s"), "2m 5s");
assert_eq!(human_readable_duration(4225.0, 0, "d h m s"), "1h 10m 25s");
assert_eq!(human_readable_duration(90061.0, 0, "d h m s"), "1d 1h 1m 1s");
}
#[test]
fn proc_wireless_spec_example() {
let content =
"Inter-| sta-| Quality | Discarded packets | Missed | WE\n \
face | tus | link level noise | nwid crypt frag retry misc | beacon | 22\n\
wlp3s0: 0000 58. -52. -256 0 0 0 0 76 0\n";
assert_eq!(
parse_proc_wireless(content),
Some(("wlp3s0".to_string(), "-52".to_string(), "-256".to_string()))
);
let two = format!("{content}wlan1: 0000 40. -60. -256 0 0 0 0 0 0\n");
assert_eq!(parse_proc_wireless(&two), None);
let bad = "wlp3s0: 0001 58. -52. -256 0 0 0 0 76 0\n";
assert_eq!(parse_proc_wireless(bad), None);
}
#[test]
fn iw_link_spec_example() {
let out = "Connected to 74:83:c2:be:76:da (on wlp3s0)\n\
\tSSID: DailyGrindGuest1\n\
\tfreq: 5745\n\
\tRX: 35192066 bytes (27041 packets)\n\
\tTX: 4090471 bytes (24287 packets)\n\
\tsignal: -52 dBm\n\
\trx bitrate: 243.0 MBit/s VHT-MCS 6 40MHz VHT-NSS 2\n\
\ttx bitrate: 240.0 MBit/s VHT-MCS 5 40MHz short GI VHT-NSS 2\n";
assert_eq!(
parse_iw_link(out),
Some(("DailyGrindGuest1".to_string(), "240".to_string()))
);
let vht = "\tSSID: x\n\ttx bitrate: 240.5 MBit/s\n";
assert_eq!(
parse_iw_link(vht),
Some(("x".to_string(), "240.5".to_string()))
);
assert_eq!(parse_iw_link("\ttx bitrate: 240.0 MBit/s\n"), None);
}
#[test]
fn wifi_bars_ladder() {
assert_eq!(wifi_bars(44), 4); assert_eq!(wifi_bars(40), 4);
assert_eq!(wifi_bars(39), 3);
assert_eq!(wifi_bars(25), 3);
assert_eq!(wifi_bars(24), 2);
assert_eq!(wifi_bars(15), 2);
assert_eq!(wifi_bars(14), 1);
assert_eq!(wifi_bars(10), 1);
assert_eq!(wifi_bars(9), 0);
}
#[test]
fn proxy_strip_forms() {
assert_eq!(
proxy_strip("http://proxy.example.com:8080/x"),
"proxy.example.com:8080"
);
assert_eq!(
proxy_strip("socks5://user:pw@10.0.0.1:1080"),
"10.0.0.1:1080"
);
assert_eq!(proxy_strip("proxy:3128"), "proxy:3128");
}
}