Skip to content
nobspromptby neg4n.dev
Esc
navigateopen⌘Jpreview
On this page

Data and lifecycle

Reference the complete NBSP_DATA schema, asynchronous update lifecycle, safe prompt escaping, and refresh behavior.

Update lifecycle

At every precmd, detached mode asks nbsp data --format nul for a framed snapshot. It replaces the global NBSP_DATA associative array only after the producer exits successfully and all 18 required schema fields, the frame terminator, and schema version have been validated. A completed background Git worker can request another load and a ZLE redraw.

Register prompt functions in nbsp_data_update_functions:

my_prompt_update() {
  # Assign PROMPT and/or RPROMPT here.
}

nbsp_data_update_functions+=(my_prompt_update)
my_prompt_update

Callbacks run after every successful complete-frame parse, even when the new values equal the previous values. This includes a reload of the preserved old cache after a Git refresh fails. A malformed, incomplete, wrong-version, or failed frame leaves the previous NBSP_DATA untouched and runs no callbacks. Callback output is not hidden or redirected.

Data schema

Key Meaning
schema_version Data contract version, currently 2
cwd Full working directory
path Abbreviated display path
status Previous command’s exact exit status
duration_ms Previous command duration in milliseconds
jobs Background job count
node_version A 1-255-byte ASCII label derived from the final NVM_BIN component (or its parent when a final bin has a preceding component), with one leading v removed before a digit; otherwise empty
git_present 1 when native repository discovery succeeds
git_valid 1 when a valid cache snapshot’s branch matches the branch read from current Git metadata
git_branch Branch name or short detached commit
git_updated_ms Snapshot wall-clock timestamp in milliseconds when git_valid=1; otherwise 0
git_staged Porcelain records with staged changes
git_modified Porcelain records with worktree changes
git_untracked Untracked porcelain records
git_conflicted Unmerged porcelain records
git_ahead Commits ahead of upstream
git_behind Commits behind upstream
git_stashes Stash count

Consumers should require a schema version they understand, address records by key rather than position, and ignore unknown keys added by a compatible future version.

Repository refresh state

On a prompt in a repository, git_present may be 1 while git_valid is 0. When current Git metadata can be read, git_branch uses that branch or short detached commit and detailed counters remain zero until a branch-matched cache snapshot is available. If current branch metadata cannot be read, a cached branch label may be exposed for context, but git_valid remains 0.

A successful refresh collects one porcelain-v2 status, rereads current branch metadata, and publishes only if both branch values still match. The next successful data-frame load can then expose git_valid=1. No particular prompt is guaranteed to be the first successful refresh: lock contention, timeout, Git failure, a branch change, or cache publication failure can defer it.

Use git_valid when your presentation needs to distinguish a confirmed zero from counters that have not arrived:

if [[ ${NBSP_DATA[git_valid]} == 0 ]]; then
  git_state=" ..."
fi

Snapshot boundaries

Each NBSP_DATA value is a point-in-time observation, not a live filesystem view. Files, branches, jobs, and environment state can change immediately after collection. Detailed Git counters come from the most recent branch-coherent cache snapshot; git_updated_ms identifies when that snapshot was collected.

precmd performs the regular foreground load and schedules a background refresh. If a refresh was already running when a foreground command began, the integration schedules a forced post-command refresh after that worker finishes. Changing directory stops tracking the old worker’s notification, and a completion is applied only while the shell is still in the directory from which that refresh was started. The detached worker can still finish its cache work. These checks prevent a known old-directory result from being applied to the new prompt, but they cannot turn the snapshot into a continuously live view.

Safe prompt text

Zsh prompt options change which bytes are interpreted during prompt expansion. Quote dynamic values before inserting them:

nbsp_prompt_quote "${NBSP_DATA[path]}"
local safe_path=$REPLY

nbsp_prompt_quote:

  • writes the quoted value to REPLY;
  • replaces control bytes with ?;
  • doubles % when PROMPT_PERCENT is enabled;
  • encodes the complete value as one non-recursive Zsh parameter expression when PROMPT_SUBST is enabled, so $, backticks, backslashes, quotes, and other shell syntax remain data;
  • doubles ! when PROMPT_BANG is enabled;
  • does not start a process.

The helper reads but never changes the current option state. Quote and display the value under the same PROMPT_PERCENT, PROMPT_SUBST, and PROMPT_BANG settings; call it again after changing any of those options. Do not insert raw fields into PROMPT, RPROMPT, or a PROMPT_SUBST expression.

Prompt substitution

Callbacks can place quoted values into the prompt string while PROMPT_SUBST performs final expansion on each redraw:

setopt promptsubst

prepare_prompt_data() {
  local path branch=
  nbsp_prompt_quote "${NBSP_DATA[path]}"
  path=$REPLY
  if [[ -n ${NBSP_DATA[git_branch]} ]]; then
    nbsp_prompt_quote "${NBSP_DATA[git_branch]}"
    branch=" [$REPLY]"
  fi
  PROMPT="${path}${branch} %# "
}

nbsp_data_update_functions+=(prepare_prompt_data)
prepare_prompt_data

Assign the helper result into the actual prompt string in the callback, as above. Hiding that result behind another parameter reference would add an extra expansion level; Zsh deliberately does not recursively rescan parameter results as new prompt source.

OSC sequences

Detached mode leaves terminal control sequences under your ownership. It updates NBSP_DATA and requests a redraw, but does not replace or interpret OSC wrappers in your prompt.

See Prompt recipes for a complete terminal-title example.