Files
dotfiles/zsh/lib/plugins.zsh
2025-12-25 15:45:29 -05:00

302 lines
8.5 KiB
Bash

# ============================================================================
# Dotfiles Plugin Manager
# ============================================================================
# A thin wrapper for managing zsh plugins without heavy frameworks.
#
# Features:
# - Simple git-based plugin installation
# - Automatic updates
# - Lazy loading support
# - Oh-My-Zsh plugin compatibility
# ============================================================================
# Prevent double-sourcing
[[ -n "$_DF_PLUGINS_LOADED" ]] && return 0
typeset -g _DF_PLUGINS_LOADED=1
# ============================================================================
# Configuration
# ============================================================================
typeset -g DF_PLUGIN_DIR="${DF_PLUGIN_DIR:-$HOME/.dotfiles/zsh/plugins}"
typeset -g DF_PLUGIN_REPOS_FILE="${DF_PLUGIN_DIR}/.repos"
# Track loaded plugins
typeset -ga DF_LOADED_PLUGINS=()
# ============================================================================
# Core Functions
# ============================================================================
# Install a plugin from GitHub
# Usage: df_plugin "zsh-users/zsh-autosuggestions" [branch]
df_plugin() {
local repo="$1"
local branch="${2:-master}"
local name="${repo##*/}"
local dir="$DF_PLUGIN_DIR/$name"
# Ensure plugin directory exists
[[ ! -d "$DF_PLUGIN_DIR" ]] && mkdir -p "$DF_PLUGIN_DIR"
# Clone if not exists
if [[ ! -d "$dir" ]]; then
echo "Installing plugin: $name..."
git clone --depth 1 --branch "$branch" "https://github.com/$repo.git" "$dir" 2>/dev/null
if [[ $? -eq 0 ]]; then
echo "✓ Installed: $name"
# Track the repo
echo "$repo|$branch" >> "$DF_PLUGIN_REPOS_FILE"
else
echo "✗ Failed to install: $name"
return 1
fi
fi
# Source the plugin
df_plugin_load "$name"
}
# Load a plugin by name
df_plugin_load() {
local name="$1"
local dir="$DF_PLUGIN_DIR/$name"
# Check if already loaded
[[ " ${DF_LOADED_PLUGINS[*]} " =~ " $name " ]] && return 0
if [[ -d "$dir" ]]; then
# Try common plugin file names
local plugin_files=(
"$dir/$name.plugin.zsh"
"$dir/$name.zsh"
"$dir/init.zsh"
"$dir/$name.sh"
)
for file in "${plugin_files[@]}"; do
if [[ -f "$file" ]]; then
source "$file"
DF_LOADED_PLUGINS+=("$name")
return 0
fi
done
echo "Warning: Could not find plugin entry point for $name"
return 1
else
echo "Plugin not found: $name"
return 1
fi
}
# Lazy load a plugin (load on first use of command)
# Usage: df_plugin_lazy "plugin-name" "command1" "command2"
df_plugin_lazy() {
local plugin="$1"
shift
local commands=("$@")
for cmd in "${commands[@]}"; do
eval "
$cmd() {
unfunction $cmd 2>/dev/null
df_plugin_load '$plugin'
$cmd \"\$@\"
}
"
done
}
# Update all plugins
df_plugin_update() {
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
df_print_func_name "Plugin Update"
echo ""
for dir in "$DF_PLUGIN_DIR"/*/; do
[[ -d "$dir/.git" ]] || continue
local name=$(basename "$dir")
df_print_step "Updating: $name"
(
cd "$dir"
git pull --quiet 2>/dev/null && \
df_print_success "$name updated" || \
df_print_warning "$name: update failed"
)
done
}
# List installed plugins
df_plugin_list() {
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
df_print_func_name "Installed Plugins"
echo ""
if [[ ! -d "$DF_PLUGIN_DIR" ]] || [[ -z "$(ls -A "$DF_PLUGIN_DIR" 2>/dev/null)" ]]; then
df_print_info "No plugins installed"
return
fi
for dir in "$DF_PLUGIN_DIR"/*/; do
[[ -d "$dir" ]] || continue
local name=$(basename "$dir")
local loaded=""
[[ " ${DF_LOADED_PLUGINS[*]} " =~ " $name " ]] && loaded=" ${DF_GREEN}(loaded)${DF_NC}"
# Get repo info if available
local repo_info=""
if [[ -d "$dir/.git" ]]; then
local remote=$(cd "$dir" && git remote get-url origin 2>/dev/null)
repo_info=" ${DF_DIM}${remote##*github.com/}${DF_NC}"
fi
df_print_indent "${name}${loaded}${repo_info}"
done
echo ""
df_print_section "Loaded Plugins"
if [[ ${#DF_LOADED_PLUGINS[@]} -gt 0 ]]; then
df_print_indent "${DF_LOADED_PLUGINS[*]}"
else
df_print_indent "(none)"
fi
}
# Remove a plugin
df_plugin_remove() {
local name="$1"
local dir="$DF_PLUGIN_DIR/$name"
[[ -z "$name" ]] && { echo "Usage: df_plugin_remove <name>"; return 1; }
if [[ ! -d "$dir" ]]; then
echo "Plugin not found: $name"
return 1
fi
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
df_confirm "Remove plugin '$name'?" || return 1
rm -rf "$dir"
# Remove from repos file
if [[ -f "$DF_PLUGIN_REPOS_FILE" ]]; then
grep -v "/$name|" "$DF_PLUGIN_REPOS_FILE" > "${DF_PLUGIN_REPOS_FILE}.tmp" 2>/dev/null || true
mv "${DF_PLUGIN_REPOS_FILE}.tmp" "$DF_PLUGIN_REPOS_FILE"
fi
df_print_success "Removed: $name"
df_print_info "Restart shell to fully unload"
}
# Install recommended plugins
df_plugin_recommended() {
source "${DOTFILES_DIR:-$HOME/.dotfiles}/zsh/lib/bootstrap.zsh" 2>/dev/null
df_print_func_name "Recommended Plugins"
echo ""
local plugins=(
"zsh-users/zsh-autosuggestions:Fish-like autosuggestions"
"zsh-users/zsh-syntax-highlighting:Syntax highlighting"
"zsh-users/zsh-completions:Additional completions"
"romkatv/zsh-defer:Deferred loading for faster startup"
"Aloxaf/fzf-tab:FZF-powered tab completion"
)
for plugin_info in "${plugins[@]}"; do
local repo="${plugin_info%%:*}"
local desc="${plugin_info#*:}"
local name="${repo##*/}"
local status=""
if [[ -d "$DF_PLUGIN_DIR/$name" ]]; then
status="${DF_GREEN}(installed)${DF_NC}"
else
status="${DF_DIM}(not installed)${DF_NC}"
fi
echo -e " ${DF_CYAN}$name${DF_NC} $status"
echo -e " ${DF_DIM}$desc${DF_NC}"
echo -e " ${DF_DIM}Install: df_plugin $repo${DF_NC}"
echo ""
done
}
# ============================================================================
# Command Interface
# ============================================================================
# Main plugin command
plugin() {
local cmd="${1:-list}"
shift 2>/dev/null || true
case "$cmd" in
install|add|i)
[[ -z "$1" ]] && { echo "Usage: plugin install <github-user/repo>"; return 1; }
df_plugin "$@"
;;
load|l)
df_plugin_load "$@"
;;
lazy)
df_plugin_lazy "$@"
;;
update|up|u)
df_plugin_update
;;
list|ls)
df_plugin_list
;;
remove|rm|r)
df_plugin_remove "$@"
;;
recommended|rec)
df_plugin_recommended
;;
help|--help|-h)
cat << 'EOF'
Dotfiles Plugin Manager
Usage: plugin <command> [args]
Commands:
install <repo> Install plugin from GitHub (e.g., zsh-users/zsh-autosuggestions)
load <name> Load an installed plugin
lazy <n> <cmds> Lazy-load plugin on command use
update Update all plugins
list List installed plugins
remove <name> Remove a plugin
recommended Show recommended plugins
Examples:
plugin install zsh-users/zsh-autosuggestions
plugin lazy zsh-nvm nvm node npm
plugin update
plugin remove zsh-autosuggestions
EOF
;;
*)
echo "Unknown command: $cmd"
echo "Use 'plugin help' for usage"
return 1
;;
esac
}
# ============================================================================
# Initialize
# ============================================================================
# Ensure plugin directory exists
[[ ! -d "$DF_PLUGIN_DIR" ]] && mkdir -p "$DF_PLUGIN_DIR"