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()),
"zshrs_cache_autoloads" => Some(rkyv_store_segments(name, RkyvStore::Autoloads)),
"zshrs_cache_scripts" => Some(rkyv_store_segments(name, RkyvStore::Scripts)),
"zshrs_cache_plugins" => Some(rkyv_store_segments(name, RkyvStore::Plugins)),
"zshrs_cache_index" => Some(rkyv_store_segments(name, RkyvStore::Index)),
"zshrs_cache_snapshots" => Some(rkyv_store_segments(name, RkyvStore::Snapshots)),
"zshrs_version" | "stryke_version" | "vimlrs_version" | "elisprs_version"
| "awkrs_version" => Some(lang_version_segments(name)),
"stryke_cache" | "vimlrs_cache" | "elisprs_cache" | "awkrs_cache" => {
Some(lang_cache_segments(name))
}
_ => 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<String, (Instant, Option<String>)>>> = OnceLock::new();
fn cached_ttl(
key: &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.to_string(), (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![]
}
#[derive(Clone, Copy)]
enum RkyvStore {
Autoloads,
Scripts,
Plugins,
Index,
Snapshots,
}
impl RkyvStore {
fn location(self, root: &std::path::Path) -> (Option<PathBuf>, Option<PathBuf>) {
match self {
RkyvStore::Autoloads => (Some(root.join("autoloads.rkyv")), None),
RkyvStore::Scripts => (Some(root.join("scripts.rkyv")), None),
RkyvStore::Index => (Some(root.join("index.rkyv")), None),
RkyvStore::Plugins => (None, Some(root.join("images"))),
RkyvStore::Snapshots => (None, Some(root.join("snapshots"))),
}
}
fn default_fg(self) -> &'static str {
match self {
RkyvStore::Autoloads => "cyan",
RkyvStore::Scripts => "green",
RkyvStore::Plugins => "blue",
RkyvStore::Index => "magenta",
RkyvStore::Snapshots => "yellow",
}
}
fn ttl_key(self) -> &'static str {
match self {
RkyvStore::Autoloads => "zshrs_cache.autoloads",
RkyvStore::Scripts => "zshrs_cache.scripts",
RkyvStore::Plugins => "zshrs_cache.plugins",
RkyvStore::Index => "zshrs_cache.index",
RkyvStore::Snapshots => "zshrs_cache.snapshots",
}
}
}
fn rkyv_store_segments(segname: &str, store: RkyvStore) -> Vec<Segment> {
if introspection_irrelevant() {
return vec![];
}
let Some(joined) = cached_ttl(store.ttl_key(), Duration::from_secs(30), || {
let root = zshrs_root();
let (count, bytes) = match store.location(&root) {
(Some(file), None) => match std::fs::metadata(&file) {
Ok(m) => (1usize, m.len()),
Err(_) => (0, 0),
},
(None, Some(dir)) => 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)),
_ => unreachable!("location() yields exactly one Some"),
};
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);
let content = match store.location(&zshrs_root()) {
(Some(_), None) => human_readable_bytes(bytes as f64),
_ => format!("{count} {}", human_readable_bytes(bytes as f64)),
};
vec![make_segment(
segname,
None,
color1(),
store.default_fg(),
"ZSHRS_CACHE_ICON",
"\u{F1C0}",
content,
)]
}
fn lang_version_segments(segname: &str) -> Vec<Segment> {
let tool = segname.strip_suffix("_version").unwrap_or(segname);
let (default_fg, default_glyph) = match tool {
"zshrs" => ("cyan", "\u{F120}"), "stryke" => ("yellow", "\u{26A1}"), "vimlrs" => ("green", "\u{E62B}"), "elisprs" => ("magenta", "\u{E632}"), _ => ("red", "\u{F120}"), };
let ttl_key: &'static str = match tool {
"stryke" => "stryke_version",
"vimlrs" => "vimlrs_version",
"elisprs" => "elisprs_version",
_ => "awkrs_version",
};
let version = if tool == "zshrs" {
Some(env!("CARGO_PKG_VERSION").to_string())
} else {
cached_ttl(ttl_key, Duration::from_secs(300), || {
let bin = path_lookup(tool)?;
let out = run_version(&bin)?;
parse_version_token(&out)
})
};
let Some(v) = version else {
return vec![]; };
vec![make_segment(
segname,
None,
color1(),
default_fg,
&format!("{}_ICON", segname.to_ascii_uppercase()),
default_glyph,
v,
)]
}
fn lang_cache_segments(segname: &str) -> Vec<Segment> {
let tool = segname.strip_suffix("_cache").unwrap_or(segname);
let (ttl_key, default_fg): (&'static str, &'static str) = match tool {
"stryke" => ("stryke_cache", "yellow"),
"vimlrs" => ("vimlrs_cache", "green"),
"elisprs" => ("elisprs_cache", "magenta"),
_ => ("awkrs_cache", "red"),
};
let Some(joined) = cached_ttl(ttl_key, Duration::from_secs(30), || {
let home_var = format!("{}_HOME", tool.to_ascii_uppercase());
let root = std::env::var(&home_var)
.map(PathBuf::from)
.unwrap_or_else(|_| {
PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(format!(".{tool}"))
});
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("cache"));
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(
segname,
None,
color1(),
default_fg,
"ZSHRS_CACHE_ICON",
"\u{F1C0}",
format!("{count} {}", human_readable_bytes(bytes as f64)),
)]
}
fn path_lookup(tool: &str) -> Option<PathBuf> {
let path = std::env::var("PATH").ok()?;
for dir in path.split(':') {
if dir.is_empty() {
continue;
}
let cand = std::path::Path::new(dir).join(tool);
if cand.is_file() {
return Some(cand);
}
}
None
}
fn run_version(bin: &std::path::Path) -> Option<String> {
let out = std::process::Command::new(bin)
.arg("--version")
.stdin(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.output()
.ok()?;
if !out.status.success() {
return None;
}
Some(String::from_utf8_lossy(&out.stdout).into_owned())
}
fn parse_version_token(out: &str) -> Option<String> {
out.lines().next()?.split_whitespace().find_map(|t| {
let t = t.strip_prefix('v').unwrap_or(t);
if t.chars().next().is_some_and(|c| c.is_ascii_digit()) {
Some(t.to_string())
} else {
None
}
})
}
#[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");
}
#[test]
fn version_token_forms() {
assert_eq!(
parse_version_token("stryke 0.9.3 (aarch64-apple-darwin)"),
Some("0.9.3".into())
);
assert_eq!(parse_version_token("awkrs v1.2.0"), Some("1.2.0".into()));
assert_eq!(parse_version_token("zshrs 0.12.15\nextra"), Some("0.12.15".into()));
assert_eq!(parse_version_token("no digits here"), None);
assert_eq!(parse_version_token(""), None);
}
}