pub mod config;
pub mod expansion;
pub mod git;
pub mod icons;
pub mod render;
pub mod segments_core;
pub mod segments_env;
pub mod segments_extra;
pub mod segments_powerline;
pub mod segments_sys;
pub mod segments_zshrs;
pub mod transient;
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
static ENGINE_ACTIVE: AtomicBool = AtomicBool::new(false);
pub fn engine_active() -> bool {
ENGINE_ACTIVE.load(Ordering::Relaxed)
}
static EXEC_START_MS: AtomicU64 = AtomicU64::new(0);
static EXEC_LAST_MS: AtomicU64 = AtomicU64::new(u64::MAX);
fn now_ms() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
pub fn note_exec_start() {
if engine_active() {
EXEC_START_MS.store(now_ms(), Ordering::Relaxed);
}
}
pub fn note_command_finished(last_status: i64) {
if !engine_active() {
return;
}
LAST_STATUS.store(last_status, Ordering::Relaxed);
let start = EXEC_START_MS.swap(0, Ordering::Relaxed);
if start != 0 {
EXEC_LAST_MS.store(now_ms().saturating_sub(start), Ordering::Relaxed);
}
}
pub fn last_exec_duration() -> Option<std::time::Duration> {
match EXEC_LAST_MS.load(Ordering::Relaxed) {
u64::MAX => None,
ms => Some(std::time::Duration::from_millis(ms)),
}
}
static LAST_STATUS: AtomicI64 = AtomicI64::new(0);
pub fn last_status() -> i64 {
LAST_STATUS.load(Ordering::Relaxed)
}
pub fn maybe_intercept_theme_source(args: &[String]) -> Option<i32> {
let path = args.first()?;
let base = path.rsplit('/').next().unwrap_or(path);
if !matches!(
base,
"powerlevel10k.zsh-theme" | "powerlevel10k.plugin.zsh" | "prompt_powerlevel10k_setup"
) {
return None;
}
ENGINE_ACTIVE.store(true, Ordering::Relaxed);
tracing::info!(target: "p10k", %path, "native p10k engine activated (theme source intercepted)");
Some(0)
}
pub fn maybe_intercept_command(name: &str, args: &[String]) -> Option<i32> {
if !engine_active() {
return None;
}
match name {
"zshrs-p10k-api" | "p10k" | "p10k-instant-prompt-finalize" => Some(p10k_api(args)),
_ => None,
}
}
thread_local! {
static USER_SEGMENT_SINK: std::cell::RefCell<Option<Vec<render::Segment>>> =
const { std::cell::RefCell::new(None) };
}
fn p10k_api(args: &[String]) -> i32 {
match args.first().map(String::as_str) {
Some("segment") => {
let mut text = String::new();
let mut icon = String::new();
let mut fg = String::new();
let mut bg = String::new();
let mut state: Option<String> = None;
let mut cond = true;
let mut i = 1;
while i < args.len() {
let take = |i: &mut usize| -> String {
*i += 1;
args.get(*i).cloned().unwrap_or_default()
};
match args[i].as_str() {
"-t" => text = take(&mut i),
"-i" => icon = take(&mut i),
"-f" => fg = take(&mut i),
"-b" => bg = take(&mut i),
"-s" => state = Some(take(&mut i)),
"-c" => cond = !take(&mut i).is_empty(),
"-e" | "-r" => {}
other => {
tracing::debug!(target: "p10k", %other, "p10k segment: unknown flag ignored")
}
}
i += 1;
}
USER_SEGMENT_SINK.with(|s| {
let mut slot = s.borrow_mut();
match slot.as_mut() {
Some(v) => {
if cond {
v.push(render::Segment {
name: String::new(), state,
content: text,
icon: if icon.is_empty() { None } else { Some(icon) },
fg,
bg,
});
}
0
}
None => {
eprintln!("zshrs: p10k: segment: can be called only during prompt rendering");
1
}
}
})
}
Some("reload") | Some("display") | Some("refresh") | None => 0,
Some("finalize") => 0,
Some(other) => {
tracing::debug!(target: "p10k", %other, "p10k API subcommand absorbed");
0
}
}
}
fn run_user_segment_fn(base: &str) -> Option<Vec<render::Segment>> {
let fname = format!("prompt_{base}");
crate::ported::utils::getshfunc(&fname)?;
USER_SEGMENT_SINK.with(|s| *s.borrow_mut() = Some(Vec::new()));
let status = crate::fusevm_bridge::try_with_executor(|exec| {
exec.execute_script(&fname).unwrap_or(-1)
})
.unwrap_or(-1);
let segs = USER_SEGMENT_SINK.with(|s| s.borrow_mut().take()).unwrap_or_default();
if status != 0 && segs.is_empty() {
tracing::debug!(target: "p10k", %fname, status, "user segment fn failed");
}
let mut segs = segs;
for s in &mut segs {
s.name = base.to_string();
}
Some(segs)
}
pub fn preprompt_render() {
if !engine_active() {
return;
}
let render_t0 = std::time::Instant::now();
struct RenderTimer(std::time::Instant);
impl Drop for RenderTimer {
fn drop(&mut self) {
let ms = self.0.elapsed().as_millis();
if ms > 100 {
tracing::info!(target: "p10k", ms, "slow preprompt_render");
} else {
tracing::debug!(target: "p10k", ms, "preprompt_render");
}
}
}
let _t = RenderTimer(render_t0);
let left_elems = config::p9k_global_arr("LEFT_PROMPT_ELEMENTS");
let right_elems = config::p9k_global_arr("RIGHT_PROMPT_ELEMENTS");
let split_lines = |elems: &[String]| -> Vec<Vec<render::Segment>> {
let mut lines: Vec<Vec<render::Segment>> = vec![Vec::new()];
for name in elems {
if name == "newline" {
lines.push(Vec::new());
continue;
}
let (base, joined) = render::is_joined_name(name);
let soc = format!(
"POWERLEVEL9K_{}_SHOW_ON_COMMAND",
base.replace('-', "_").to_ascii_uppercase()
);
if crate::ported::params::getsparam(&soc).is_some()
|| crate::ported::params::getaparam(&soc).is_some()
{
tracing::debug!(target: "p10k", %name, "SHOW_ON_COMMAND segment hidden at prompt paint");
continue;
}
if !expansion::show_on_upglob(base) {
tracing::debug!(target: "p10k", %name, "SHOW_ON_UPGLOB: no parent match — hidden");
continue;
}
let built = segments_core::build_segment(base)
.or_else(|| segments_env::build_segment(base))
.or_else(|| segments_sys::build_segment(base))
.or_else(|| segments_extra::build_segment(base))
.or_else(|| segments_powerline::build_segment(base))
.or_else(|| segments_zshrs::build_segment(base))
.or_else(|| run_user_segment_fn(base));
match built {
Some(mut segs) => {
if joined {
for s in &mut segs {
s.name = name.clone();
}
}
lines.last_mut().expect("lines never empty").extend(segs);
}
None => {
tracing::debug!(target: "p10k", %name, "segment not implemented — skipped")
}
}
}
lines
};
let left_lines = split_lines(&left_elems);
let right_lines = split_lines(&right_elems);
let (prompt, rprompt) = render::render_prompt(&left_lines, &right_lines);
crate::ported::params::setsparam("PROMPT", &prompt);
crate::ported::params::setsparam("RPROMPT", &rprompt);
}