Open-Source Wikis

/

Starship

/

Subsystems

/

Context

starship/starship

Context

The Context is the per-prompt object passed to every module. It owns the parsed configuration, the current and logical working directories, the user's environment, lazy caches for the directory contents and Git repo, the active shell, the render target, and (for the Claude Code statusline) Claude session data. Source: src/context.rs.

Shape

pub struct Context<'a> {
    pub config: StarshipConfig,
    pub current_dir: PathBuf,
    pub logical_dir: PathBuf,
    dir_contents: OnceLock<Result<DirContents, std::io::Error>>,
    pub properties: Properties,
    repo: OnceLock<Result<Repo, Box<gix::discover::Error>>>,
    pub shell: Shell,
    pub target: Target,
    pub width: usize,
    pub env: Env<'a>,
    #[cfg(test)]
    pub cmd: HashMap<&'a str, Option<CommandOutput>>,
    #[cfg(test)]
    pub root_dir: tempfile::TempDir,
    #[cfg(feature = "battery")]
    pub battery_info_provider: &'a (dyn BatteryInfoProvider + Send + Sync),
    pub root_config: StarshipRootConfig,
    pub claude_code_data: Option<Box<ClaudeCodeData>>,
    _marker: PhantomData<&'a ()>,
}

The lifetime 'a ties Context to anything it borrows from the environment (notably the Env<'a> for env-var mocking).

Construction

Context::new(properties, target) is called once per starship prompt invocation. It:

  1. Detects the shell from $STARSHIP_SHELL.
  2. Resolves the current directory via arguments.pathenv::current_dir()$PWDarguments.logical_path.
  3. Resolves the logical directory the same way, but starting from --logical-path.
  4. Calls Context::new_with_shell_and_path, which parses starship.toml via StarshipConfig::initialize, normalizes pipestatus and status_code, canonicalizes current_dir (with dunce for Windows), and returns the populated Context.

StarshipRootConfig::load (or default() if no config) is materialized eagerly because every module reads the root config (e.g. command_timeout, scan_timeout, palette).

Properties

Properties is the clap-parsed bag of CLI flags Starship's init scripts pass on every redraw:

Field CLI flag Where it comes from
path: Option<PathBuf> --path Some shells (e.g. Powershell on UNC paths) need to override cwd
logical_path: Option<PathBuf> --logical-path "the path the user typed", separate from the canonicalized one
status_code: Option<String> --status $?
pipestatus: Option<Vec<String>> --pipestatus ${PIPESTATUS[*]} (bash) / $pipestatus (zsh) / $pipestatus (fish)
cmd_duration: Option<String> --cmd-duration Milliseconds the previous command took
keymap: Option<String> --keymap vi-mode for character
jobs: Option<u64> --jobs Number of background jobs
terminal_width: usize --terminal-width $COLUMNS, or 0 if unknown
shlvl: Option<i64> --shlvl $SHLVL

Defined in Properties using clap::Parser.

Lazy state

Two fields are wrapped in OnceLock so they pay their cost only once per prompt:

  • dir_contents — populated on the first call to Context::dir_contents() or try_begin_scan(). Reads the directory once into a DirContents (a HashSet of files, file names, folders, and extensions). Time-bounded by scan_timeout (default 30ms) and uses a worker thread + mpsc::recv_timeout to abort runaway reads.
  • repo — populated on the first call to Context::get_repo(). Discovers the git directory via gix, opens it with custom permissions (config reading enabled even for "reduced" trust), and packages the result into a Repo struct.

This is what lets multiple modules look at the same directory without rescanning, and lets git_status/git_metrics/git_branch share the same gix handle.

Environment access

Modules call context.get_env("FOO") and context.get_env_os("FOO") rather than std::env::var. Why? Because in tests the underlying Env<'a> (defined in src/utils/env.rs) is a HashMap rather than a real OS environment, and ModuleRenderer::env injects fixtures into it. The wrapper enforces that test runs are deterministic.

context.get_home() similarly checks for $HOME first when cfg(test), then falls back to dirs::home_dir.

Process execution

context.exec_cmd(cmd, args) is the canonical way to run an external command from inside a module. It:

  1. Looks up (cmd, args) in Context::cmd (test-only), returning a mocked CommandOutput if present.
  2. Falls back to crate::utils::mock_cmd (also test-only), which encodes "well-known" command outputs for things like node --version. The matched commands are listed in mock_cmd in src/utils/mod.rs; the Contributing guide warns that adding a new exec call requires a matching mock.
  3. Otherwise spawns a real process via crate::utils::create_command(cmd) (which sets predictable defaults), runs it inside the current dir, and bounds it by command_timeout (default 500ms) using process_control::Control::time_limit.

context.exec_cmds_return_first lets a module try a list of binaries (e.g., python then python3) and pick the first one that responds.

Detection helpers

context.try_begin_scan()? // ScanDir; sets ./files/extensions/folders to match
       .set_files(&["Cargo.toml"])
       .set_extensions(&["rs"])
       .set_folders(&["src"])
       .is_match();

context.begin_ancestor_scan()  // ScanAncestors; walks upward from cwd
       .set_files(&[".hg"])
       .scan();

context.detect_env_vars(&["VIRTUAL_ENV", "!CONDA_DEFAULT_ENV"]) // ! = negation

ScanDir operates on the cached DirContents. ScanAncestors walks current_dir.ancestors() and stops when it finds a match — used by the hg modules to find a repo root.

The Repo cache

Context::get_repo returns a &Repo containing:

  • repo: ThreadSafeRepository — gix repo handle for thread-local conversion.
  • branch: Option<String> — short branch name (or HEAD when detached).
  • workdir: Option<PathBuf>None for bare repos.
  • path: PathBuf — the .git directory.
  • state: Option<gix::state::InProgress> — rebase / merge / etc.
  • remote: Option<Remote>name and branch of the upstream.
  • fs_monitor_value_is_true: bool — read from the repo's core.fsmonitor to enable optimizations in git_status.

get_current_branch (free function in src/context.rs) handles symbolic refs and detached-HEAD shortenings.

Render targets

pub enum Target {
    Main,
    Right,
    Continuation,
    Profile(String),
}

Selected in src/main.rs from the combination of --right, --continuation, and --profile=<name>. Target::Profile looks up the named profile in user_profiles (from [profiles] in TOML) first, falling back to internal_profiles (the default_profiles() shipped in code). The Claude Code statusline uses Target::Profile("claude-code").

Shells

pub enum Shell {
    Bash, Fish, Ion, PowerShell, Pwsh, Zsh, Elvish, Tcsh, Nu, Xonsh, Cmd, Unknown,
}

Detected from $STARSHIP_SHELL, set by the init scripts. A few modules (character, shell, cmd_duration's init flow) branch on shell to handle quirks (e.g. fish's default keymap meaning vi-normal).

Tests

Tests construct Context via default_context() in src/test/mod.rs, which stamps a fresh tempfile::TempDir as root_dir. Helpers like context_path rewrite "absolute" paths (/etc/foo) into paths under that tempdir so tests cannot leak onto the host filesystem.

Entry points for modification

  • New shell support: add a Shell variant + dispatch in Context::get_shell and update the matching init script under src/init/.
  • New Properties flag (e.g. a new shell concept that needs to flow into modules): add a field to Properties, plumb it through Context, and update every init script that should set it.
  • New repo metadata exposed to git modules: add a field to Repo in src/context.rs and populate it during the OnceLock init in Context::get_repo.
  • New context-wide cache: wrap your data in OnceLock<Result<...>>, add a getter that calls get_or_init, and document the failure mode.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Context – Starship wiki | Factory