use crate::extensions::p10k::config::{p9k_global, p9k_param};
use crate::extensions::p10k::render::Segment;
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 {
"zshrs_daemon" => Some(zshrs_daemon_segments()),
"zshrs_workers" => Some(zshrs_workers_segments()),
"zshrs_jit" => Some(zshrs_jit_segments()),
"zshrs_cache" => Some(zshrs_cache_segments()),
"zshrs_history" => Some(zshrs_history_segments()),
"stryke" => Some(stryke_segments()),
_ => None,
}
}
fn color1() -> &'static str {
if p9k_global("COLOR_SCHEME", "dark") == "light" {
"7"
} else {
"0"
}
}
fn decode_g(s: &str) -> String {
getkeystring(s).0
}
fn seg_icon_or(segment: &str, state: Option<&str>, key: &str, default_glyph: &str) -> String {
let probed = p9k_param(segment, state, key, "\u{1}");
if probed == "\u{1}" {
return default_glyph.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,
default_glyph: &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 = seg_icon_or(name, state, icon_key, default_glyph);
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,
}
}
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 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 compact_count(n: i64) -> String {
if n < 10_000 {
n.to_string()
} else if n < 1_000_000 {
format!("{}k", n / 1_000)
} else {
let m = n as f64 / 1_000_000.0;
if m >= 10.0 {
format!("{m:.0}M")
} else {
format!("{m:.1}M")
}
}
}
fn jit_content(count: usize, bytes: u64) -> String {
if count == 0 {
"jit".to_string()
} else {
format!("{count} {}", human_readable_bytes(bytes as f64))
}
}
fn zshrs_root() -> PathBuf {
if let Some(custom) = std::env::var_os("ZSHRS_HOME") {
PathBuf::from(custom)
} else {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".zshrs")
}
}
fn introspection_irrelevant() -> bool {
if crate::IS_ZSH_MODE.load(Ordering::Relaxed) {
return true;
}
crate::fusevm_bridge::try_with_executor(|exec| {
exec.zsh_compat || exec.bash_compat || exec.posix_mode
})
.unwrap_or(false)
}
fn zshrs_daemon_segments() -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
use crate::daemon_presence::Mode;
let (state, fg, content) = match crate::daemon_presence::current() {
Mode::Present => ("PRESENT", "green", "up"),
Mode::Absent => ("ABSENT", "8", "down"),
Mode::Unknown | Mode::Disabled => return vec![],
};
vec![make_segment(
"zshrs_daemon",
Some(state),
color1(),
fg,
"ZSHRS_DAEMON_ICON",
"\u{F0AE}",
content.to_string(),
)]
}
fn zshrs_workers_segments() -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
let Some((size, queued)) = crate::fusevm_bridge::try_with_executor(|exec| {
(exec.worker_pool.size(), exec.worker_pool.queue_depth())
}) else {
return vec![];
};
let content = if queued > 0 {
format!("{size}+{queued}")
} else {
size.to_string()
};
vec![make_segment(
"zshrs_workers",
None,
color1(),
"cyan",
"ZSHRS_WORKERS_ICON",
"\u{F085}",
content,
)]
}
fn zshrs_jit_segments() -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
let Some(joined) = cached_ttl("zshrs_jit.scan", Duration::from_secs(15), || {
let dir = fusevm::JitCompiler::new().jit_cache_dir()?;
let (mut count, mut bytes) = (0usize, 0u64);
if let Ok(rd) = std::fs::read_dir(&dir) {
for entry in rd.flatten() {
if entry.file_name().to_string_lossy().ends_with(".fjit") {
count += 1;
bytes += entry.metadata().map(|m| m.len()).unwrap_or(0);
}
}
}
Some(format!("{count}\u{1f}{bytes}"))
}) else {
return vec![];
};
let mut f = joined.split('\u{1f}');
let count: usize = f.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let bytes: u64 = f.next().and_then(|s| s.parse().ok()).unwrap_or(0);
vec![make_segment(
"zshrs_jit",
None,
color1(),
"magenta",
"ZSHRS_JIT_ICON",
"\u{F0E7}",
jit_content(count, bytes),
)]
}
fn zshrs_cache_segments() -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
let Some(joined) = cached_ttl("zshrs_cache.scan", Duration::from_secs(30), || {
let root = zshrs_root();
let rkyv_in = |dir: &std::path::Path| -> (usize, u64) {
std::fs::read_dir(dir)
.map(|rd| {
rd.flatten()
.filter(|e| e.file_name().to_string_lossy().ends_with(".rkyv"))
.fold((0usize, 0u64), |(c, b), e| {
(c + 1, b + e.metadata().map(|m| m.len()).unwrap_or(0))
})
})
.unwrap_or((0, 0))
};
let (c1, b1) = rkyv_in(&root);
let (c2, b2) = rkyv_in(&root.join("images"));
let (count, bytes) = (c1 + c2, b1 + b2);
if count == 0 {
return None; }
Some(format!("{count}\u{1f}{bytes}"))
}) else {
return vec![];
};
let mut f = joined.split('\u{1f}');
let count: usize = f.next().and_then(|s| s.parse().ok()).unwrap_or(0);
let bytes: u64 = f.next().and_then(|s| s.parse().ok()).unwrap_or(0);
vec![make_segment(
"zshrs_cache",
None,
color1(),
"blue",
"ZSHRS_CACHE_ICON",
"\u{F1C0}",
format!("{count} {}", human_readable_bytes(bytes as f64)),
)]
}
fn zshrs_history_segments() -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
let Some(count_s) = cached_ttl("zshrs_history.count", Duration::from_secs(60), || {
let db = zshrs_root().join("zshrs_history.db");
if !db.exists() {
return None;
}
crate::history::with_session_engine(|e| e.count().ok())
.flatten()
.map(compact_count)
}) else {
return vec![];
};
vec![make_segment(
"zshrs_history",
None,
color1(),
"yellow",
"ZSHRS_HISTORY_ICON",
"\u{F1DA}",
count_s,
)]
}
fn stryke_segments() -> Vec<Segment> {
tracing::debug!(
target: "p10k",
"stryke segment: no registration probe in lib.rs (STRYKE_HANDLER private) — hidden"
);
vec![]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compact_count_bands() {
assert_eq!(compact_count(0), "0");
assert_eq!(compact_count(9_999), "9999");
assert_eq!(compact_count(10_000), "10k");
assert_eq!(compact_count(478_123), "478k");
assert_eq!(compact_count(999_999), "999k");
assert_eq!(compact_count(1_234_567), "1.2M");
assert_eq!(compact_count(12_345_678), "12M");
}
#[test]
fn human_readable_bytes_forms() {
assert_eq!(human_readable_bytes(0.0), "0B");
assert_eq!(human_readable_bytes(1024.0), "1K");
assert_eq!(human_readable_bytes(1_363_148.8), "1.3M");
assert_eq!(human_readable_bytes(5.0 * 1024.0 * 1024.0), "5M");
}
#[test]
fn jit_content_empty_vs_populated() {
assert_eq!(jit_content(0, 0), "jit");
assert_eq!(jit_content(42, 1_363_149), "42 1.3M");
assert_eq!(jit_content(1, 1024), "1 1K");
}
}