diff --git a/autoenv/autoenv.plugin.zsh b/autoenv/autoenv.plugin.zsh new file mode 100644 index 0000000..d048e31 --- /dev/null +++ b/autoenv/autoenv.plugin.zsh @@ -0,0 +1,108 @@ +# Automatically update the environment when the current working directory +# changes, this is a reimplementation of the ideas found in the repository +# https://github.com/Tarrasch/zsh-autoenv stripped down to bare essentials. +# +# The secret sauce can be found at the bottom of this file, where the `chpwd` +# hook function `_autoenv_chpwd` is added. + +# Global entered directories array. +_autoenv_entered=() + +# Check if the given file is authorized, if not prompt the user to authorize, +# ignore, or view the file. Authorized files and their modified times are +# stored in the ~/.cache/autoenv/authorized file to make authorization +# persistent. +_autoenv_authorized() { + local file=$1 + # If autoenv cache directory does not exist, create it. + local cache_dir=~/.cache/autoenv + ! [ -d $cache_dir ] && mkdir -p $cache_dir + local authorized_file=$cache_dir/authorized + # Define a map to hold authorized key value pairs. + typeset -A authorized=() + # If the authorized file exists, load it into the map. + [ -f $authorized_file ] && typeset -A authorized=(`cat $authorized_file`) + # If the given file has been authorized, i.e. the modified time matches that + # held in the authorized file, return. + local modified_time=`zstat +mtime $file` + [ "$authorized[$file]" = "$modified_time" ] && return + # Prompt to authorize file. + while true; do + read "answer?Authorize $file [Y/n/v]? " + case "$answer" in + y|Y|yes|'') # Authorize the file. + authorized[$file]=$modified_time; break ;; + n|N|no) return 1 ;; # Do not authorize the file. + v|V|view) cat $file ;; # View the file. + esac + done + # Store authorized associative array in authorized file. + echo ${(kv)authorized} > $authorized_file +} + +# Source an enter script and add its directory to the global entered +# directories array. +_autoenv_enter() { + local entered=$1 + # If entered exists in the entered directories array, return. + (( ${+_autoenv_entered[${_autoenv_entered[(i)$entered]}]} )) && return + # If the enter script is not authorized, return. + _autoenv_authorized $entered/.enter || return + # Source the enter script. + source $entered/.enter + # Add the entered directory to the global entered array. + _autoenv_entered+=$entered +} + +# Source an exit script and remove its directory from the global entered +# directories array. +_autoenv_exit() { + local entered=$1 + # If the exit script is not authorized, return. + _autoenv_authorized $entered/.exit || return + # Source the exit script. + source $entered/.exit + # Remove the entered directory from the global entered array. + _autoenv_entered[${_autoenv_entered[(i)$entered]}]=() +} + +# Find all directories containing a .enter file by searching up the directory +# tree starting in the current directory. +_autoenv_find_enter_directories() { + local current=$PWD + # If an enter script is found in the current directory, return it. + [ -f $current/.enter ] && echo $current + # Loop until an enter script or the root directory is found. + while true; do + # Go up one directory and make the path absolute. + local next=$current/..; local next=${next:A} + # If an enter script is found in the current directory, return it. + [ -f $next/.enter ] && echo $next + # If the current directory equals the next directory we are done, otherwise + # update the current directory. + [[ $current == $next ]] && return || local current=$next + done +} + +# A chpwd hook function which automatically sources enter and exit scripts to +# setup local environments for directory and its subdirectories. +_autoenv_chpwd() { + local entered + # Loop over the reversed entered directory array. + for entered in ${(aO)_autoenv_entered}; do + # If the the current directory was previously entered then exit. + ! [[ $PWD/ == $entered/* ]] && _autoenv_exit $entered + done + # Find all enter script directories, store them in an array. + local enter_dirs=(`_autoenv_find_enter_directories`) + # Loop over reversed enter script directories array, so enter scripts found + # last are sourced first, then source all enter scripts. + for enter in ${(aO)enter_dirs}; do _autoenv_enter $enter; done +} + +# Register the autoenv chpwd hook. +autoload -U add-zsh-hook +add-zsh-hook chpwd _autoenv_chpwd + +# Ensure autoenv is activated in the current directory on first load. +_autoenv_chpwd diff --git a/zshrc b/zshrc index 48cbe52..7bfd75c 100644 --- a/zshrc +++ b/zshrc @@ -12,6 +12,7 @@ source-plugin() { source-plugin zsh-autosuggestions source-plugin zsh-history-substring-search source-plugin zsh-syntax-highlighting +source-plugin autoenv # Disable non end-of-line autosuggest accept widgets ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=(end-of-line vi-end-of-line)