From 6f720ba98504da18aff3dea3c27c8e3f6ed2d8f9 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Sat, 18 May 2024 01:13:18 +0100 Subject: [PATCH] feat(shell): create cached-eval helper to improve shell startup speed. --- zsh/zoxide.zsh | 2 +- zshenv | 51 ++++++++++++++++++- zshrc | 134 +++++++++++++++++++++++++++++-------------------- 3 files changed, 130 insertions(+), 57 deletions(-) diff --git a/zsh/zoxide.zsh b/zsh/zoxide.zsh index beed6ba..70b9bde 100644 --- a/zsh/zoxide.zsh +++ b/zsh/zoxide.zsh @@ -3,7 +3,7 @@ # if command-exists zoxide; then - eval "$(zoxide init --cmd zox zsh)" + cached-eval "$(command -v zoxide)" zoxide init --cmd zox zsh # Use functions to allow regular zsh completion for cd to work. cd() { __zoxide_z "$@"; } diff --git a/zshenv b/zshenv index 42e9183..530aab6 100644 --- a/zshenv +++ b/zshenv @@ -74,6 +74,54 @@ source-if-exists() { fi } +# cached-eval executes a command with arguments and caches the output. On +# subsequent calls, if the source file has not changed, the output is sourced +# from the cache instead of re-executing the command. This optimizes performance +# for commands that are costly to execute but result in the same output unless +# their source files change. +# +# Arguments: +# +# $1 - source_file: The path to the source file that the command depends on. +# If this file is newer than the cache, the command is +# re-executed and the cache is updated. +# $2 - cmd: The command to execute. +# $@ - args: Additional arguments to pass to the command. +# +# Example usage: +# +# cached-eval "$(command -v direnv)" direnv hook zsh +# cached-eval "$(command -v mise)" mise activate zsh +# +# The above commands will cache the output of `direnv hook zsh` and `mise +# activate zsh` respectively. If the source file is newer than the cache, the +# command is re-executed and cache is updated. +cached-eval() { + local source_file="$1" + local cmd="$2" + shift 2 + local args="$@" + local full_cmd="$cmd $args" + local cache_dir="${ZSH_CACHED_EVAL_DIR:-$HOME/.local/share/zsh/cached-eval}" + + if [[ -z "$(command -v "$cmd")" ]]; then + echo "cached-eval: Command not found: $cmd" >&2 + return 1 + fi + + local cache_hash="$(echo -n "$full_cmd" | md5sum | awk '{print $1}')" + local cache_file="${cache_dir}/$(basename "$cmd")_${cache_hash}.zsh" + + if [[ ! -f "$cache_file" || "$source_file" -nt "$cache_file" ]]; then + mkdir -p "$cache_dir" + echo "cached-eval: Updating cache for: $full_cmd --> $cache_file" >&2 + echo -e "#\n# Generated by cached-eval: $full_cmd\n#\n" >| "$cache_file" + eval "$full_cmd" >>| "$cache_file" + fi + + source "$cache_file" +} + # ============================================================================== # System Environment Setup # ============================================================================== @@ -98,6 +146,7 @@ fi export DOTZSH_SITEFUNS="$DOTZSH/site-functions" export ZSH_COMPLETIONS="$HOME/.local/share/zsh/completions" +export ZSH_CACHED_EVAL_DIR="$HOME/.local/share/zsh/cached-eval" # Ensure basic systems paths are in desired order path_prepend "/bin" @@ -129,7 +178,7 @@ fi # Homebrew on Apple Silicon if [ -f "/opt/homebrew/bin/brew" ]; then - eval "$(/opt/homebrew/bin/brew shellenv)" + cached-eval /opt/homebrew/bin/brew /opt/homebrew/bin/brew shellenv fi if command-exists brew; then diff --git a/zshrc b/zshrc index af486f9..3712499 100644 --- a/zshrc +++ b/zshrc @@ -52,58 +52,72 @@ zinit light-mode wait lucid atload"!_zsh_autosuggest_start" \ for @zsh-users/zsh-autosuggestions # ============================================================================== -# Completion +# Helpers # ============================================================================== -# Group completions by type under group headings -zstyle ':completion:*' group-name '' -zstyle ':completion:*:descriptions' format '%B%d%b' - -# Improve selection of Makefile completions - from: -# https://github.com/zsh-users/zsh-completions/issues/541#issuecomment-384223016 -zstyle ':completion:*:make:*:targets' call-command true -zstyle ':completion:*:make:*' tag-order targets - -if [ -d "$ZSH_COMPLETIONS" ]; then fpath=("$ZSH_COMPLETIONS" $fpath); fi -if [ -d "$DOTZSH_SITEFUNS" ]; then fpath=("$DOTZSH_SITEFUNS" $fpath); fi -if [ -d "$BREW_SITEFUNS" ]; then fpath=("$BREW_SITEFUNS" $fpath); fi - -autoload -Uz compinit -compinit - -# setup-completions is a helper function to setup completions for a given -# command. It takes the command name, the source of the completion, and the -# command to run to generate the completions. +# setup-completions is a helper function to set up shell completions for a given +# command. It generates Zsh completion scripts and places them in the specified +# completions directory. If the completion file already exists, it checks if the +# source file has been updated and regenerates the completions if necessary. # -# Source should be a file that the completions are generated from. For example, -# for rustup, the source is the rustup binary. If completions file has already -# been generated, the source file is used to determine if the completions need -# to be re-generated. +# Arguments: # -# The command to run to generate the completions should be a command that -# generates zsh completions. For example, for rustup, the command is: -# -# rustup completions zsh +# $1 - cmd: The name of the command for which completions are being +# set up. +# $2 - source: The source file used to determine if completions need to +# be re-generated. For example, the binary file of the +# command (e.g., rustup). +# $@ - args: The command to run to generate the completions. This +# should produce Zsh completion scripts. # # Example usage: # # setup-completions rustup "$(command -v rustup)" rustup completions zsh # -# This will generate the completions for rustup and place them in the -# ZSH_COMPLETIONS directory. +# This example sets up completions for the 'rustup' command by running +# 'rustup completions zsh', and places the generated completion script in the +# appropriate completions directory. If the source file is newer than the target +# completion file, the command is re-executed and the completion script is +# updated. +# +# The completions are placed in the directory specified by the ZSH_COMPLETIONS +# environment variable. If ZSH_COMPLETIONS is not set, the completions are +# placed in $HOME/.zsh/completions by default. setup-completions() { local cmd="$1" local source="$2" - shift 2 - local target - target="${ZSH_COMPLETIONS}/_${cmd}" + local setup_cmd="$3" + shift 3 - if [ ! -f "$target" ] || [ "$target" -ot "$source" ]; then - echo "Setting up completion for $cmd -- $target" - mkdir -p "$(dirname "$target")" - "$@" > "$target" - chmod +x "$target" - autoload -U compinit && compinit + local target_dir="${ZSH_COMPLETIONS:-$HOME/.zsh/completions}" + local target_file="${target_dir}/_${cmd}" + + if [[ -z "$(command -v "$cmd")" ]]; then + echo "setup-completions: Command not found: $cmd" >&2 + return 1 + fi + + if [[ -z "$(command -v "$setup_cmd")" ]]; then + echo "setup-completions: Command not found: $setup_cmd" >&2 + return 1 + fi + + if [[ -z "$cmd" || -z "$source" || -z "$setup_cmd" ]]; then + echo "setup-completions: Missing required arguments." >&2 + return 1 + fi + + # Check if the target completion file needs to be updated + if [[ ! -f "$target_file" || "$source" -nt "$target_file" ]]; then + echo "setup-completions: Setting up completion for $cmd --> $target_file" >&2 + mkdir -p "$target_dir" + "$setup_cmd" "$@" >| "$target_file" + chmod +x "$target_file" + + # Only run compinit if not already loaded + if ! (whence -w compinit &> /dev/null); then + autoload -U compinit && compinit + fi fi } @@ -175,6 +189,26 @@ convert-alias-to-function() { eval "$(convert-alias-source-to-function-source "$alias_source")" } +# ============================================================================== +# Completion +# ============================================================================== + +# Group completions by type under group headings +zstyle ':completion:*' group-name '' +zstyle ':completion:*:descriptions' format '%B%d%b' + +# Improve selection of Makefile completions - from: +# https://github.com/zsh-users/zsh-completions/issues/541#issuecomment-384223016 +zstyle ':completion:*:make:*:targets' call-command true +zstyle ':completion:*:make:*' tag-order targets + +if [ -d "$ZSH_COMPLETIONS" ]; then fpath=("$ZSH_COMPLETIONS" $fpath); fi +if [ -d "$DOTZSH_SITEFUNS" ]; then fpath=("$DOTZSH_SITEFUNS" $fpath); fi +if [ -d "$BREW_SITEFUNS" ]; then fpath=("$BREW_SITEFUNS" $fpath); fi + +autoload -Uz compinit +compinit + # ============================================================================== # Edit command line # ============================================================================== @@ -197,7 +231,7 @@ fi # If available, make sure to load direnv shell hook before mise. if command-exists direnv; then - eval "$(direnv hook zsh)" + cached-eval "$(command -v direnv)" direnv hook zsh fi MISE_HOME="$HOME/.local/share/mise" @@ -205,19 +239,14 @@ MISE_ZSH_INIT="$MISE_HOME/shell/init.zsh" export MISE_INSTALL_PATH="$MISE_HOME/bin/mise" if ! command-exists mise; then - read -q 'REPLY?mise is not installed, install with `curl https://mise.jdx.dev/install.sh | sh`? [y/N]:' && - echo && curl https://mise.jdx.dev/install.sh | sh + read -q 'REPLY?mise is not installed, install with `curl https://mise.run | sh`? [y/N]:' && + echo && curl https://mise.run | sh fi if command-exists mise; then alias mi="mise" - if [ ! -f "$MISE_ZSH_INIT" ] || [ "$MISE_ZSH_INIT" -ot "$MISE_INSTALL_PATH" ]; then - mkdir -p "$(dirname "$MISE_ZSH_INIT")" - "$MISE_INSTALL_PATH" activate zsh > "$MISE_ZSH_INIT" - fi - source "$MISE_ZSH_INIT" - + cached-eval "$MISE_INSTALL_PATH" mise activate zsh setup-completions mise "$MISE_INSTALL_PATH" mise completions zsh fi @@ -231,13 +260,8 @@ if ! command-exists starship && [ -f "$MISE_INSTALL_PATH" ]; then fi if command-exists starship; then - eval "$(starship init zsh --print-full-init)" - - _starship() { - unset -f _starship - eval "$(starship completions zsh)" - } - compctl -K _starship starship + cached-eval "$(command -v starship)" starship init zsh --print-full-init + setup-completions starship "$(command -v starship)" starship completions zsh else echo "WARN: starship not found, skipping prompt setup" >&2 echo " install with: mise install starship" >&2