antidote

repository·main·Indexed 23 days ago

https://github.com/mattmc3/antidote

A high-performance Zsh plugin manager that provides a feature-complete implementation of the legacy Antibody and Antigen plugin managers. It supports static and dynamic loading modes, plugin pinning via Git SHAs, and the use of .zsh_plugins.txt files to manage GitHub repositories and Oh My Zsh plugins.

Tokens
11.9K
Snippets
29
Records
81
Agent score
55%

What's inside antidote

  1. Use dynamic bundling for flexible plugin loading

    main

    If you prefer a style similar to Antigen, you can use dynamic bundling. Instead of a separate file, you source the output of antidote init and call antidote bundle directly in your .zshrc.

    Antidote caches the generated load scripts in your ANTIDOTE_HOME to ensure subsequent shell starts remain fast. You can speed up loading further by enabling zcompile for these cache files.

    Example configuration in .zshrc:

    source /path/to/antidote/antidote.zsh
    source <(antidote init)
    antidote bundle zsh-users/zsh-autosuggestions
    antidote bundle ohmyzsh/ohmyzsh path:lib
    antidote bundle ohmyzsh/ohmyzsh path:plugins/git
  2. Understand the antidote script generation pipeline

    main

    When you run antidote_bundle, the following sequence occurs:

    1. Parsing: bundle_parser reads the input from stdin.
    2. Critical Check: bundle_check_critical validates the input. If critical errors (like conflicting pins/branches) are found, the process aborts immediately.
    3. Parallel Cloning: bulk_clone identifies missing repositories and initiates parallel git clone operations. If any bundle uses kind:defer, the zsh-defer bundle is prioritized.
    4. Pin Syncing & Zcompiling: If __has_pins__ is set, bundle_sync_pins runs sequentially to sync to specific SHAs, followed by bundle_zcompile_pass.
    5. Parallel Scripting: bundle_scripter_parallel generates one zsh_script per row in parallel. To maintain the semantic order of plugins, it writes to numbered temporary files (%03d) and then concatenates them.
    6. Cleanup: bundle_dir_cleanup_pass removes any duplicate clones that don't match the current path-style.
    7. Verification & Emission: The system verifies all required directories exist on disk and finally emits the compiled script.
  3. Understand the antidote architecture and execution model

    main

    antidote is a Zsh plugin manager designed for fast startup. It operates using two distinct execution layers:

    1. Parent Shell (functions/): Code that runs directly in your interactive Zsh session. This layer is responsible for mutations that affect your shell, such as source-ing files, modifying fpath, PATH, or using autoload.
    2. Subprocess (antidote.zsh): The core engine that runs in a separate Zsh process. This allows the engine to use setopt or create global variables without leaking them into your interactive shell. Most heavy lifting (parsing, cloning, updating) happens here.

    Key takeaway for developers: If you are extending antidote and need to modify the user's shell environment, your function must reside in functions/ and use a hyphenated name (e.g., antidote-foo). If the logic is purely computational or filesystem-based, it should be an underscore-prefixed function in antidote.zsh (e.g., antidote_foo).

  4. Understand the parsed bundle matrix structure

    main

    Antidote uses a central global associative array, _parsed_bundles, to store all information extracted from your bundle text. Because Zsh lacks nested arrays, it uses a flat matrix with composite keys in the format "$i,$key" (where $i is a 1-indexed row number).

    Matrix Keys

    Matrix-level keys:

    • __count__: Total number of rows.
    • __has_pins__, __has_errors__, __has_critical__: Flags used to optimize subsequent processing passes.

    Per-row keys (_parsed_bundles[row_index,key]):

    User-provided keys (stored verbatim):

    • kind, path, branch, pin, conditional, autoload, pre, post, fpath-rule

    Computed keys (prefixed with __):

    • __bundle__: The original bundle word.
    • __type__: The detected bundle_type.
    • __name__: The bundle name.
    • __url__: The URL used.
    • __short__: The short name.
    • __dir__: The directory on disk.
    • __lineno__: The line number in the source.
    • __error__: Error details.
    • __severity__: The error severity level.
  5. Use `using:` directives for subpaths

    main

    The using: directive allows you to set a context for subsequent bare words in your bundle file, reducing repetition.

    There are two flavors:

    1. Repo using: The context becomes a clone. Subsequent bare words are treated as path: subpaths of that repository.
    2. Path using: The context is a local directory. Subsequent bare words are treated as full paths.

    Note: The line containing the using: directive itself does not produce a bundle row.

  6. Compare Static vs Dynamic mode

    main

    antidote offers two modes of operation depending on your priority (startup speed vs. immediacy):

    Static Mode (Default)

    Best for: Maximum startup speed.

    • Uses antidote load to generate a single static Zsh script (.zsh_plugins.zsh) from your .zsh_plugins.txt file.
    • The script is only regenerated if the .txt file is newer than the generated .zsh file or if the $ANTIDOTE_HOME/.antidote.load check file is missing.
    • At steady state, startup is just a single source of one flat file with no subprocess overhead.

    Dynamic Mode

    Best for: Immediate feedback/development.

    • Initialized via source <(antidote init).
    • Replaces the antidote function with a router that sends bundle commands to functions/antidote-bundle-dynamic and everything else to the subprocess.
    • Each antidote bundle line is cached in $ANTIDOTE_HOME/.dynamic/<hash>.zsh to avoid a subprocess per line.
    • The hash is calculated based on zstyles, file mtimes, using: context, bundle arguments, and the antidote version.
  7. Optimize performance with static plugin files and deferred loading

    main

    For ultra-high performance, you can use a static plugin file. This involves using antidote bundle to generate a .zsh file from your .txt plugin list. You can also use kind:defer in your .zsh_plugins.txt for plugins that support deferred loading (e.g., via zsh-defer).

    Example workflow for lazy-loading antidote and generating a static load file only when the plugin list changes:

    # .zsh_plugins.txt
    # some plugins support deferred loading
    zdharma-continuum/fast-syntax-highlighting kind:defer
    zsh-users/zsh-autosuggestions kind:defer
    zsh-users/zsh-history-substring-search kind:defer
    # .zshrc
    # Lazy-load antidote and generate the static load file only when needed
    zsh_plugins=${ZDOTDIR:-$HOME}/.zsh_plugins
    if [[ ! ${zsh_plugins}.zsh -nt ${zsh_plugins}.txt ]]; then
      (
        source /path-to-antidote/antidote.zsh
        antidote bundle <${zsh_plugins}.txt >${zsh_plugins}.zsh
      )
    fi
    source ${zsh_plugins}.zsh
  8. Manage bundle pins and snapshots

    main

    Pins allow you to lock a bundle to a specific Git SHA for reproducibility.

    Pinning

    • Syntax: Use the pin:<sha> annotation. You must use the full 40-character SHA; short SHAs are not guaranteed to be unique.
    • Persistence: Pin state is stored in the clone's Git config as antidote.pin. This ensures the pin survives even if you regenerate your static files.
    • Updating: antidote update will skip any bundle that has a pin: set.
    • Ephemeral Pins: If you set the environment variable ANTIDOTE_EPHEMERAL_PIN=true, antidote will check out the specified SHA without writing the antidote.pin config to the repository. This is useful for snapshot restoration.

    Snapshots

    Snapshots are text files named snapshot-YYYYmmdd-HHMMSSZ.txt located in _ANTIDOTE_SNAPSHOT_DIR. They contain lines in the format repo kind:clone pin:<sha>. Because they are plain text, they are easily diffable and can be restored using the standard antidote_bundle logic.

  9. Understand antidote variable naming conventions

    main

    When interacting with or extending antidote, it is important to distinguish between user-set variables and internal script variables based on their naming prefixes:

    • ANTIDOTE_* (Reserved for users/external processes): These are variables that you, the user, or the antidote-zsh process can set.
      • Environment variables: ANTIDOTE_CONFIG, ANTIDOTE_HOME, ANTIDOTE_TMPDIR, ANTIDOTE_PROFILE, ANTIDOTE_PROFILE_OUT, and ANTIDOTE_EPHEMERAL_PIN.
      • Process boundary variables: ANTIDOTE_ZSTYLES, ANTIDOTE_DYNAMIC, ANTIDOTE_USING_CTX, and ANTIDOTE_ZSH.
    • _ANTIDOTE_* (Internal script variables): These are computed by antidote.zsh for its own use and are not intended to be set externally. Examples include _ANTIDOTE_GIT_SITE, _ANTIDOTE_PATH_STYLE, and _ANTIDOTE_COLOR.

    Using the correct prefix ensures you are not attempting to override internal state or accidentally relying on a variable that is meant to be private to the script.

  10. Configure antidote using zstyles

    main

    Antidote prefers the use of zstyle for configuration rather than new environment variables. Environment variables should only be used for bootstrap ordering (like ANTIDOTE_CONFIG) or when crossing the process boundary.

    For general configuration, use the Zsh zstyle idiom. If you are looking for test-specific knobs, they are located under the :antidote:test:* context.

  11. How the parent/subprocess boundary works

    main

    Because the core engine runs in a subprocess, it cannot directly access the parent shell's state (like zstyle settings or completions). State is passed across the boundary via specific environment variables managed by antidote-zsh:

    • ANTIDOTE_ZSTYLES: The serialized output of zstyle -L ':antidote:*', which is eval'd in the subprocess.
    • ANTIDOTE_HOME: The directory for antidote data.
    • ANTIDOTE_TMPDIR: Temporary directory.
    • ANTIDOTE_DYNAMIC: Indicates if dynamic mode is active.
    • ANTIDOTE_USING_CTX: Serialized _antidote_using_context, allowing using: directives to persist across separate subprocess calls in dynamic mode.

    Note: To retrieve state from the subprocess in the parent shell (e.g., for completions), you must shell out to an antidote command and parse its output.

  12. Load antidote in .zshrc

    main

    To use antidote in your Zsh configuration, source the antidote.zsh script and then call antidote load pointing to your plugin file.

    # .zshrc
    source /path-to-antidote/antidote.zsh
    antidote load ${ZDOTDIR:-$HOME}/.zsh_plugins.txt