pub mod api;
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;
pub mod wizard;
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
static ENGINE_ACTIVE: AtomicBool = AtomicBool::new(false);
pub static P10K_ROOT_DIR: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
pub fn p10k_root_dir() -> Option<String> {
P10K_ROOT_DIR.lock().unwrap().clone()
}
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);
if let Some(dir) = path.rsplit_once('/').map(|(d, _)| d.to_string()) {
*P10K_ROOT_DIR.lock().unwrap() = Some(dir);
}
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 {
if args.len() == 1 && args[0] == "finalize" {
return 0; }
let Some(cmd) = args.first().map(String::as_str) else {
api::print_usage(api::USAGE, true);
return 1;
};
match cmd {
"segment" => p10k_segment(&args[1..]),
"display" => p10k_display(&args[1..]),
"configure" => {
if args.len() > 1 {
api::print_usage(api::CONFIGURE_USAGE, true); return 1;
}
wizard::run(false)
}
"reload" => {
if args.len() > 1 {
api::print_usage(api::RELOAD_USAGE, true); return 1;
}
api::FORCE_REINIT.store(true, Ordering::Relaxed); 0
}
"help" => {
let sub = args.get(1).map(String::as_str);
let known = matches!(
sub,
None | Some("segment")
| Some("display")
| Some("configure")
| Some("reload")
| Some("finalize")
| Some("help")
);
if known || args.len() == 1 {
api::print_usage(api::help_usage(sub), false);
0
} else {
api::print_usage(api::USAGE, true);
1
}
}
"finalize" => {
api::print_usage(api::FINALIZE_USAGE, true);
1
}
"clear-instant-prompt" => 0,
_ => {
api::print_usage(api::USAGE, true);
1
}
}
}
fn p10k_segment(rest: &[String]) -> i32 {
let mut state: Option<String> = None;
let mut bg = String::new(); let mut bg_set = false;
let mut fg = String::new();
let mut icon = String::new();
let mut cond = true; let mut text = String::new();
let mut refr = false; let mut i = 0;
let mut positional_seen = false;
let arg_at = |idx: usize| rest.get(idx + 1).cloned().unwrap_or_default();
while i < rest.len() {
match rest[i].as_str() {
"-s" => {
state = Some(arg_at(i));
i += 1;
}
"-b" => {
bg = arg_at(i);
bg_set = true;
i += 1;
}
"-f" => {
fg = arg_at(i);
i += 1;
}
"-i" => {
icon = arg_at(i);
i += 1;
}
"-c" => {
cond = !arg_at(i).is_empty();
i += 1;
}
"-t" => {
text = arg_at(i);
i += 1;
}
"-r" => refr = true,
"+r" => refr = false,
"-e" | "+e" => {}
"-h" => {
api::print_usage(api::SEGMENT_USAGE, false);
return 0;
}
s if s.starts_with('-') || s.starts_with('+') => {
api::print_usage(api::SEGMENT_USAGE, true);
return 1;
}
_ => {
positional_seen = true;
}
}
i += 1;
}
if positional_seen {
api::print_usage(api::SEGMENT_USAGE, true);
return 1;
}
let _ = refr;
let _ = bg_set;
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 => {
api::print_usage(
"%1F[ERROR]%f %Bp10k segment%b: can be called only during prompt rendering.",
true,
);
1
}
}
})
}
fn p10k_display(rest: &[String]) -> i32 {
if rest.is_empty() {
api::print_usage(api::DISPLAY_USAGE, true);
return 1;
}
let mut dump = false; let mut reset = false; let mut i = 0;
while i < rest.len() {
match rest[i].as_str() {
"-a" => dump = true, "-r" => reset = true, "-h" => { api::print_usage(api::DISPLAY_USAGE, false);
return 0;
}
s if s.starts_with('-') && s.len() > 1 => {
api::print_usage(api::DISPLAY_USAGE, true); return 1;
}
_ => break, }
i += 1;
}
let operands: Vec<&str> = rest[i..].iter().map(String::as_str).collect();
if dump {
let pats = if operands.is_empty() { vec!["*"] } else { operands };
let pairs = api::display_dump(&pats);
crate::ported::exec::set_array("reply", pairs);
if reset {
preprompt_render(); }
return 0;
}
if reset && operands.is_empty() {
api::display_reset();
preprompt_render();
return 0;
}
let mut changed = false;
for op in operands {
let Some((pat, list)) = op.split_once('=') else {
api::print_usage(api::DISPLAY_USAGE, true);
return 1;
};
let states: Vec<&str> = list.split(',').filter(|s| !s.is_empty()).collect();
if api::display_set(pat, &states) {
changed = true;
}
}
if changed {
preprompt_render();
}
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 api::is_hidden(base) {
tracing::debug!(target: "p10k", %name, "hidden by p10k display toggle");
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);
}