use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
pub static FORCE_REINIT: AtomicBool = AtomicBool::new(false);
#[derive(Default)]
struct Layout {
left: Vec<Vec<String>>,
right: Vec<Vec<String>>,
}
struct Slot {
name: String,
alias: Option<String>,
}
static LAYOUT: Mutex<Option<Layout>> = Mutex::new(None);
static DISPLAY_STATE: Mutex<Option<HashMap<String, String>>> = Mutex::new(None);
pub fn set_layout(left: Vec<Vec<String>>, right: Vec<Vec<String>>) {
*LAYOUT.lock().unwrap() = Some(Layout { left, right });
}
fn slots() -> Vec<Slot> {
let g = LAYOUT.lock().unwrap();
let mut out = vec![
Slot { name: "empty_line".into(), alias: None },
Slot { name: "ruler".into(), alias: None },
];
let Some(layout) = g.as_ref() else { return out };
let n = layout.left.len().max(layout.right.len()) as i64;
for i in 1..=n {
let j = -n + i - 1;
let both = |suffix: &str| Slot {
name: format!("{i}{suffix}"),
alias: Some(format!("{j}{suffix}")),
};
out.push(both(""));
out.push(both("/left_frame"));
out.push(both("/right_frame"));
out.push(both("/left"));
out.push(both("/right"));
out.push(both("/gap"));
let idx = (i - 1) as usize;
for name in layout.left.get(idx).into_iter().flatten() {
out.push(both(&format!("/left/{name}")));
}
for name in layout.right.get(idx).into_iter().flatten() {
out.push(both(&format!("/right/{name}")));
}
}
out
}
fn default_state(name: &str) -> String {
let enabled = match name {
"empty_line" => crate::extensions::p10k::config::p9k_global("PROMPT_ADD_NEWLINE", "") == "true",
"ruler" => crate::extensions::p10k::config::p9k_global("SHOW_RULER", "") == "true",
_ => return "show".to_string(),
};
if !enabled || crate::p10k::tty_is_new() {
"hide".to_string()
} else if crate::p10k::transient::transient_enabled().is_none() {
"print".to_string()
} else {
"show".to_string()
}
}
fn state_of(map: &HashMap<String, String>, name: &str) -> String {
map.get(name).cloned().unwrap_or_else(|| default_state(name))
}
pub fn part_state(name: &str) -> String {
let g = DISPLAY_STATE.lock().unwrap();
match g.as_ref() {
Some(m) => state_of(m, name),
None => default_state(name),
}
}
fn matching(slots: &[Slot], pattern: &str) -> Vec<usize> {
slots
.iter()
.enumerate()
.filter(|(_, s)| {
crate::extensions::p10k::shared::glob_name_matches(pattern, &s.name)
|| s.alias
.as_deref()
.is_some_and(|a| crate::extensions::p10k::shared::glob_name_matches(pattern, a))
})
.map(|(i, _)| i)
.collect()
}
pub fn is_hidden(name: &str) -> bool {
let g = DISPLAY_STATE.lock().unwrap();
g.as_ref().is_some_and(|m| m.get(name).is_some_and(|s| s == "hide"))
}
pub fn display_set(pattern: &str, states: &[&str]) -> bool {
if states.is_empty() {
return false;
}
let all = slots();
let mut g = DISPLAY_STATE.lock().unwrap();
let map = g.get_or_insert_with(HashMap::new);
let mut changed = false;
for k in matching(&all, pattern) {
let name = &all[k].name;
let cur = state_of(map, name);
let new = if states.len() == 1 {
states[0].to_string()
} else {
match states.iter().rposition(|s| *s == cur) {
Some(i) => states.get(i + 1).unwrap_or(&states[0]).to_string(),
None => states[0].to_string(),
}
};
if new == cur {
continue;
}
map.insert(name.clone(), new);
changed = true;
}
changed
}
pub fn display_dump(patterns: &[&str]) -> Vec<String> {
let all = slots();
let g = DISPLAY_STATE.lock().unwrap();
let empty = HashMap::new();
let map = g.as_ref().unwrap_or(&empty);
let mut out = Vec::new();
for pat in patterns {
for k in matching(&all, pat) {
out.push(all[k].name.clone());
out.push(state_of(map, &all[k].name));
}
}
out
}
pub fn display_reset() {
if let Some(map) = DISPLAY_STATE.lock().unwrap().as_mut() {
map.clear();
}
}
pub const USAGE: &str = "Usage: %2Fp10k%f %Bcommand%b [options]
Commands:
%Bconfigure%b run interactive configuration wizard
%Breload%b reload configuration
%Bsegment%b print a user-defined prompt segment
%Bdisplay%b show, hide or toggle prompt parts
%Bhelp%b print this help message
Print help for a specific command:
%2Fp10k%f %Bhelp%b command";
pub const SEGMENT_USAGE: &str = r##"Usage: %2Fp10k%f %Bsegment%b [-h] [{+|-}re] [-s state] [-b bg] [-f fg] [-i icon] [-c cond] [-t text]
Print a user-defined prompt segment. Can be called only during prompt rendering.
Options:
-t text segment's main content; will undergo prompt expansion: '%%F{blue}%%*%%f' will
show as %F{blue}%*%f; default is empty
-i icon segment's icon; default is empty
-r icon is a symbolic reference that needs to be resolved; for example, 'LOCK_ICON'
+r icon is already resolved and should be printed literally; for example, '⭐';
this is the default; you can also use $'\u2B50' if you don't want to have
non-ascii characters in source code
-b bg background color; for example, 'blue', '4', or '#0000ff'; empty value means
transparent background, as in '%%k'; default is black
-f fg foreground color; for example, 'blue', '4', or '#0000ff'; empty value means
default foreground color, as in '%%f'; default is empty
-s state segment's state for the purpose of applying styling options; if you want to
to be able to use POWERLEVEL9K parameters to specify different colors or icons
depending on some property, use different states for different values of that
property
-c condition; if empty after parameter expansion and process substitution, the
segment is hidden; this is an advanced feature, use with caution; default is '1'
-e segment's main content will undergo parameter expansion and process
substitution; the content will be surrounded with double quotes and thus
should quote its own double quotes; this is an advanced feature, use with
caution
+e segment's main content should not undergo parameter expansion and process
substitution; this is the default
-h print this help message
Example: 'core' segment tells you if there is a file name 'core' in the current directory.
- Segment's icon is '⭐'.
- Segment's text is the file's size in bytes.
- If you have permissions to delete the file, state is DELETABLE. If not, it's PROTECTED.
zmodload -F zsh/stat b:zstat
function prompt_core() {
local size=()
if ! zstat -A size +size core 2>/dev/null; then
# No 'core' file in the current directory.
return
fi
if [[ -w . ]]; then
local state=DELETABLE
else
local state=PROTECTED
fi
p10k segment -s $state -i '⭐' -f blue -t ${size[1]}b
}
To enable this segment, add 'core' to POWERLEVEL9K_LEFT_PROMPT_ELEMENTS or
POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS.
Example customizations:
# Override default foreground.
POWERLEVEL9K_CORE_FOREGROUND=red
# Override foreground when DELETABLE.
POWERLEVEL9K_CORE_DELETABLE_BACKGROUND=green
# Override icon when PROTECTED.
POWERLEVEL9K_CORE_PROTECTED_VISUAL_IDENTIFIER_EXPANSION='❎'
# Don't show file size when PROTECTED.
POWERLEVEL9K_CORE_PROTECTED_CONTENT_EXPANSION=''"##;
pub const CONFIGURE_USAGE: &str = "Usage: %2Fp10k%f %Bconfigure%b
Run interactive configuration wizard.";
pub const RELOAD_USAGE: &str = "Usage: %2Fp10k%f %Breload%b
Reload configuration.";
pub const FINALIZE_USAGE: &str = "Usage: %2Fp10k%f %Bfinalize%b
Perform the final stage of initialization. Must be called at the very end of zshrc.";
pub const DISPLAY_USAGE: &str = r##"Usage: %2Fp10k%f %Bdisplay%b part-pattern=state-list...
Show, hide or toggle prompt parts. If called from zle, the current
prompt is refreshed.
Usage: %2Fp10k%f %Bdisplay%b -a [part-pattern]...
Populate array `reply` with states of prompt parts matching the patterns.
If no patterns are supplied, assume `*`.
Usage: %2Fp10k%f %Bdisplay%b -r
Redisplay prompt.
Parts:
empty_line empty line (duh)
ruler ruler; if POWERLEVEL9K_RULER_CHAR=' ', it's essentially another
new_line
N prompt line number N, 1-based; counting from the top if positive,
from the bottom if negative
N/left_frame left frame on the Nth line
N/left left prompt on the Nth line
N/gap gap between left and right prompts on the Nth line
N/right right prompt on the Nth line
N/right_frame right frame on the Nth line
N/left/S segment S within N/left (dir, time, etc.)
N/right/S segment S within N/right (dir, time, etc.)
Part States:
show the part is displayed
hide the part is not displayed
print the part is printed in precmd; only applicable to empty_line and
ruler; unlike show, the effects of print cannot be undone with hide;
print used to look better after `clear` but this is no longer the
case; it's best to avoid it unless you know what you are doing
part-pattern is a glob pattern for parts. Examples:
*/kubecontext all kubecontext prompt segments, regardless of where
they are
1/(right|right_frame) all prompt segments and frame from the right side of
the first line
state-list is a comma-separated list of states. Must have at least one element.
If more than one, states will rotate.
Example: Bind Ctrl+P to toggle right prompt.
function toggle-right-prompt() { p10k display '*/right'=hide,show; }
zle -N toggle-right-prompt
bindkey '^P' toggle-right-prompt
Example: Print the current state of all prompt parts:
typeset -A reply
p10k display -a '*'
printf '%%-32s = %%q\n' ${(@kv)reply} | sort
"##;
pub fn help_usage(sub: Option<&str>) -> &'static str {
match sub {
None => USAGE,
Some("segment") => SEGMENT_USAGE,
Some("configure") => CONFIGURE_USAGE,
Some("reload") => RELOAD_USAGE,
Some("finalize") => FINALIZE_USAGE,
Some("display") => DISPLAY_USAGE,
Some("help") => USAGE,
_ => USAGE,
}
}
pub fn print_usage(s: &str, to_stderr: bool) {
let (expanded, _, _) = crate::ported::prompt::promptexpand(s, 0, None);
if to_stderr {
eprintln!("{expanded}");
} else {
println!("{expanded}");
}
}
#[cfg(test)]
mod tests {
use super::*;
fn layout() {
display_reset();
set_layout(
vec![vec!["dir".into(), "vcs".into()], vec!["prompt_char".into()]],
vec![vec!["time".into()], vec![]],
);
}
#[test]
fn display_hide_show_toggle() {
let _g = crate::test_util::global_state_lock();
layout();
assert!(!is_hidden("1/left/dir"));
assert!(display_set("1/left/dir", &["hide"]));
assert!(is_hidden("1/left/dir"));
assert!(!display_set("1/left/dir", &["hide"]));
assert!(display_set("1/left/dir", &["show"]));
assert!(!is_hidden("1/left/dir"));
assert!(display_set("1/left/dir", &["hide", "show"]));
assert!(is_hidden("1/left/dir"));
display_reset();
assert!(!is_hidden("1/left/dir"));
}
#[test]
fn negative_index_aliases_the_same_slot() {
let _g = crate::test_util::global_state_lock();
layout();
assert!(display_set("-1/left/prompt_char", &["hide"]));
assert!(is_hidden("2/left/prompt_char"));
assert!(!is_hidden("1/left/dir"));
display_reset();
}
#[test]
fn dump_reports_canonical_name_state_pairs() {
let _g = crate::test_util::global_state_lock();
layout();
display_set("1/right/time", &["hide"]);
assert_eq!(
display_dump(&["1/right/time"]),
vec!["1/right/time".to_string(), "hide".to_string()]
);
assert_eq!(
display_dump(&["1/left/*"]),
vec!["1/left/dir", "show", "1/left/vcs", "show"]
);
display_reset();
}
}