commit e04b71932dfab7d06da0a397849ccb14d798d342 Author: Taylor Bockman Date: Sun May 31 13:18:44 2026 -0700 Init hardened zsh config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e009a77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Private shell config +.bash_private +.private +.zsh_private +*.private +*.local +*.secret +*.secrets + +# Environment files +.env +.env.* +!.env.example + +# Shell history +.zsh_history +.bash_history +.python_history +.lesshst +.psql_history +.mysql_history +.sqlite_history + +# SSH / keys / certs +id_rsa +id_ed25519 +*.pem +*.key +*.crt +*.p12 +*.pfx + +# Cloud credentials +.aws/ +.gcp/ +.azure/ +.kube/config + +# macOS / editor junk +.DS_Store +*.swp +*.swo +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3a1cd5 --- /dev/null +++ b/README.md @@ -0,0 +1,182 @@ +# ZSH Configuration + +A modular ZSH configuration designed to be portable, easy to maintain, and friendly to both Linux and macOS. + +The main idea is simple: keep the top-level shell startup files small, then split aliases, functions, exports, prompt setup, keybindings, and OS-specific behavior into separate files. + +## Layout + +| File | Purpose | +|---|---| +| `zshrc` | Main interactive shell configuration. Sources the modular config files. | +| `zprofile` | Login shell configuration. Sources `zshrc` where appropriate. | +| `aliases.zsh` | Shell aliases. | +| `functions.zsh` | Utility function definitions. | +| `exports.zsh` | Cross-platform environment variables and PATH setup. | +| `initializations.zsh` | Tool initialization code, such as language managers or runtime setup. | +| `keybinds.zsh` | Custom ZSH keybindings. | +| `ps1.zsh` | Prompt configuration. | +| `macrc` | macOS-specific configuration, especially Homebrew paths and macOS-only tools. | +| `scripts/` | Helper scripts used by this configuration. | + +## Installation + +Clone the repository: + +```sh +git clone git.xchg.sh:angrygoats/zsh-v2.git ~/zsh-v2 +``` + +Back up any existing ZSH files: + +```sh +mv ~/.zshrc ~/.zshrc.backup 2>/dev/null || true +mv ~/.zprofile ~/.zprofile.backup 2>/dev/null || true +``` + +Symlink the main files: + +```sh +ln -s ~/zsh-v2/zshrc ~/.zshrc +ln -s ~/zsh-v2/zprofile ~/.zprofile +``` + +Reload your shell: + +```sh +source ~/.zshrc +``` + +## Private Configuration + +Private or machine-local values should not be committed to this repository. + +The following files are automatically sourced if present: + +```text +$HOME/.bash_private +$HOME/.private +$HOME/.zsh_private +``` + +Use these for secrets, tokens, private exports, work-specific paths, or anything machine-specific that should not live in Git. + +## OS-Specific Configuration + +macOS-specific configuration belongs in: + +```text +$HOME/.macrc +``` + +This is the right place for Homebrew paths, macOS-only tools, GUI application CLI paths, and Apple Silicon versus Intel Homebrew differences. + +Linux-specific configuration can be placed in a Linux-specific file if needed. + +## PATH Management + +This configuration uses ZSH’s `path` array where possible instead of repeatedly prepending strings to `$PATH`. + +At the end of shell initialization, PATH entries should be deduplicated with: + +```zsh +typeset -U path PATH +``` + +This keeps path ordering predictable while avoiding duplicate entries. + +## Git Completion + +Git completion support is installed automatically on first run into: + +```text +$HOME/.zsh +``` + +The setup downloads: + +```text +git-completion.bash +_git +``` + +from the official Git repository. + +If the download fails, manually place both completion files in `$HOME/.zsh` and reload the shell. + +## Syntax Highlighting + +If the installed ZSH version is new enough, `zsh-syntax-highlighting` is automatically cloned into: + +```text +$HOME/.zsh/zsh-syntax-highlighting +``` + +It is sourced last, which is required for `zsh-syntax-highlighting` to work correctly with most ZSH configurations. + +## Rust + +Rust is initialized from: + +```text +$HOME/.cargo/env +``` + +if that file exists. + +This exposes standard Rust tooling such as: + +```text +cargo +rustc +rustfmt +clippy +rust-analyzer +``` + +when installed through `rustup`. + +## Go + +If Go is installed, this configuration sets: + +```zsh +GOPATH=$HOME/.go +``` + +and adds the Go binary directory to `PATH`. + +## macOS Notes + +On macOS, machine-specific Homebrew setup should live in `macrc`, not in the shared cross-platform exports file. + +Common macOS tools configured there include: + +```text +Homebrew +LLVM / clangd / clang-format +OpenSSL +OpenJDK +Qt +Tcl/Tk +Ghostty +Sublime Text CLI +``` + +The Sublime Text CLI symlink should be created once per machine, not on every shell startup: + +```sh +sudo mkdir -p /usr/local/bin +sudo ln -sf "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" /usr/local/bin/subl +``` + +## Design Goals + +This configuration aims to be: + +- Modular +- Portable across Linux and macOS +- Safe to version-control +- Friendly to private machine-local overrides +- Predictable about `PATH` ordering +- Minimal enough to debug when something goes wrong diff --git a/aliases.zsh b/aliases.zsh new file mode 100644 index 0000000..d741bec --- /dev/null +++ b/aliases.zsh @@ -0,0 +1,63 @@ +# Alias +if [[ "$OS_TYPE" != "gnu-linux" ]]; then + alias python='python3' +fi + +# rlwrap provides readline wrapping for programs. +# This alias makes the SBCL REPL usable. +alias sbcl='rlwrap sbcl' + +# Useful git aliases +alias gita='git add' +alias gitap='git add -p' +alias gitc='git commit' +alias gitp='git push' +alias gits='git status' +alias gitd='git diff' + +alias k8='kubectl' + +# Good overrides + +alias ls='ls --color=auto' +alias grep='grep --colour=auto' +alias egrep='egrep --colour=auto' +alias fgrep='fgrep --colour=auto' + +# Don't nuke the computer +alias rm='rm -i' + +# Confirm before overwriting +alias cp='cp -i' + +# Default free to sizes in megabytes +alias free='free -m' + +# Better ls - lists more information for each file +alias ll='ls -lh' + +# Less is a better more +alias more=less + +# df in human readable sizes +alias df='df -h' + +# I use Neovim +if [ -x "$(command -v nvim)" ]; then + alias vim=nvim +fi + +# Human readable ls for the current directory +if [[ "$OS_TYPE" != "gnu-linux" ]]; then + # OS X lt + alias lt='du -sh * | sort -h' +else + alias lt='ls --human-readable --size -1 -S --classify' +fi + + +# Count files in the current directory +alias count='find . -type f | wc -l' + +# Generate sha1 hashes on the fly +alias sha1='openssl sha1' diff --git a/exports.zsh b/exports.zsh new file mode 100644 index 0000000..96e24b6 --- /dev/null +++ b/exports.zsh @@ -0,0 +1,76 @@ +#!/bin/zsh + +# ------------------------- +# Editor +# ------------------------- +if command -v vim >/dev/null 2>&1; then + export EDITOR=vim +else + export EDITOR=vi +fi + +# ------------------------- +# C / C++ compiler defaults +# ------------------------- +if command -v clang >/dev/null 2>&1; then + export CC="$(command -v clang)" +elif command -v gcc >/dev/null 2>&1; then + export CC="$(command -v gcc)" +fi + +if command -v clang++ >/dev/null 2>&1; then + export CXX="$(command -v clang++)" +elif command -v g++ >/dev/null 2>&1; then + export CXX="$(command -v g++)" +fi + +# ------------------------- +# History +# ------------------------- +# Note: HISTCONTROL is bash-specific, but harmless. +# ZSH history dedupe should usually be configured with setopt elsewhere. +export HISTCONTROL=ignoredups + +# ------------------------- +# User paths +# ------------------------- +path=("$HOME/bin" $path) +path=("$HOME/.local/bin" $path) + +# ------------------------- +# Go +# ------------------------- +if command -v go >/dev/null 2>&1; then + export GOPATH="$HOME/.go" + path=("$(go env GOPATH)/bin" $path) +fi + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + # If the Go install guide was followed for Linux, + # /usr/local/go will contain the go binary. + [[ -d "/usr/local/go/bin" ]] && path=("/usr/local/go/bin" $path) +fi + +# ------------------------- +# Ruby / RVM +# ------------------------- +if [[ -d "$HOME/.rvm/bin" ]]; then + path=("$HOME/.rvm/bin" $path) +fi + +# ----------------------------- +# Rust / Cargo - Let rustup win +# ----------------------------- +if [[ -f "$HOME/.cargo/env" ]]; then + . "$HOME/.cargo/env" +fi + +if [[ -d "$HOME/.cargo/bin" ]]; then + path=("$HOME/.cargo/bin" $path) +fi +# ------------------------- +# OpenSSL fallback +# ------------------------- +if command -v openssl >/dev/null 2>&1; then + export OPENSSL_BIN="$(command -v openssl)" +fi diff --git a/functions.zsh b/functions.zsh new file mode 100644 index 0000000..870997a --- /dev/null +++ b/functions.zsh @@ -0,0 +1,12 @@ +#!/usr/zsh + +parse_git_branch() { + git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1) /' +} + +# If you ever needed weather... +weather() { + curl wttr.in +} + + diff --git a/initializations.zsh b/initializations.zsh new file mode 100644 index 0000000..ef96d44 --- /dev/null +++ b/initializations.zsh @@ -0,0 +1,19 @@ +#!/bin/zsh + +if [[ "$OSTYPE" == linux-gnu* ]]; then + export PATH="/usr/local/bin:$PATH" + export PYENV_ROOT="$HOME/.pyenv" + + [[ -d "$PYENV_ROOT/bin" ]] && export PATH="$PYENV_ROOT/bin:$PATH" + + if command -v pyenv >/dev/null 2>&1; then + eval "$(pyenv init -)" + + if pyenv commands | grep -qx "virtualenv-init"; then + eval "$(pyenv virtualenv-init -)" + fi + fi +fi + +# RVM configuration +[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" diff --git a/keybinds.zsh b/keybinds.zsh new file mode 100644 index 0000000..c75fa72 --- /dev/null +++ b/keybinds.zsh @@ -0,0 +1,3 @@ +if command -v setxkbmap >/dev/null 2>&1 && [[ "$XDG_SESSION_TYPE" == "x11" ]]; then + setxkbmap -option caps:escape +fi diff --git a/macrc b/macrc new file mode 100644 index 0000000..2b6c2c8 --- /dev/null +++ b/macrc @@ -0,0 +1,51 @@ +#!/bin/zsh + +# ------------------------- +# macOS-specific setup +# ------------------------- + +# Prefer Apple Silicon Homebrew, fall back to Intel Homebrew. +if [[ -d "/opt/homebrew" ]]; then + export HOMEBREW_PREFIX="/opt/homebrew" +elif [[ -d "/usr/local/Homebrew" || -d "/usr/local/Cellar" ]]; then + export HOMEBREW_PREFIX="/usr/local" +fi + +if [[ -n "$HOMEBREW_PREFIX" ]]; then + [[ -d "$HOMEBREW_PREFIX/bin" ]] && path=("$HOMEBREW_PREFIX/bin" $path) + [[ -d "$HOMEBREW_PREFIX/sbin" ]] && path=("$HOMEBREW_PREFIX/sbin" $path) + + # LLVM / clangd / clang-format + if [[ -d "$HOMEBREW_PREFIX/opt/llvm/bin" ]]; then + path=("$HOMEBREW_PREFIX/opt/llvm/bin" $path) + export LDFLAGS="-L$HOMEBREW_PREFIX/opt/llvm/lib ${LDFLAGS:-}" + export CPPFLAGS="-I$HOMEBREW_PREFIX/opt/llvm/include ${CPPFLAGS:-}" + fi + + # OpenSSL + if [[ -d "$HOMEBREW_PREFIX/opt/openssl@3" ]]; then + export OPENSSL_ROOT_DIR="$HOMEBREW_PREFIX/opt/openssl@3" + export LDFLAGS="-L$OPENSSL_ROOT_DIR/lib ${LDFLAGS:-}" + export CPPFLAGS="-I$OPENSSL_ROOT_DIR/include ${CPPFLAGS:-}" + export PKG_CONFIG_PATH="$OPENSSL_ROOT_DIR/lib/pkgconfig:${PKG_CONFIG_PATH:-}" + elif [[ -d "$HOMEBREW_PREFIX/opt/openssl" ]]; then + export OPENSSL_ROOT_DIR="$HOMEBREW_PREFIX/opt/openssl" + export LDFLAGS="-L$OPENSSL_ROOT_DIR/lib ${LDFLAGS:-}" + export CPPFLAGS="-I$OPENSSL_ROOT_DIR/include ${CPPFLAGS:-}" + export PKG_CONFIG_PATH="$OPENSSL_ROOT_DIR/lib/pkgconfig:${PKG_CONFIG_PATH:-}" + fi + + # OpenJDK + [[ -d "$HOMEBREW_PREFIX/opt/openjdk/bin" ]] && path=("$HOMEBREW_PREFIX/opt/openjdk/bin" $path) + + # Qt + [[ -d "$HOMEBREW_PREFIX/opt/qt/bin" ]] && path=("$HOMEBREW_PREFIX/opt/qt/bin" $path) + + # Tcl/Tk + [[ -d "$HOMEBREW_PREFIX/opt/tcl-tk/bin" ]] && path=("$HOMEBREW_PREFIX/opt/tcl-tk/bin" $path) +fi + +# Ghostty CLI +if [[ -d "/Applications/Ghostty.app/Contents/MacOS" ]]; then + path=("/Applications/Ghostty.app/Contents/MacOS" $path) +fi diff --git a/ps1.zsh b/ps1.zsh new file mode 100644 index 0000000..b38e1ab --- /dev/null +++ b/ps1.zsh @@ -0,0 +1,10 @@ +#!/bin/zsh + +setopt prompt_subst +autoload -Uz vcs_info +precmd () { vcs_info } + +# Format = [] +zstyle ':vcs_info:*' formats ' %F{green}[%b]%f' + +PS1='%B%n@%m%b %F{blue}%2~%f${vcs_info_msg_0_} %B⇨%b ' \ No newline at end of file diff --git a/scripts/cgen.sh b/scripts/cgen.sh new file mode 100755 index 0000000..a10f999 --- /dev/null +++ b/scripts/cgen.sh @@ -0,0 +1,109 @@ +#! /usr/bin/env bash + +name=$1 +cmake_version=$2 +c_standard=$3 + +# Finds the directory of this script +if [[ "$OS_TYPE" == "linux-gnu" ]]; then + script_dir=$(cd "$( dirname "`readlink -f ${BASH_SOURCE[0]}`")" && pwd) +else + script_dir=$(cd "$( dirname "`greadlink -f ${BASH_SOURCE[0]}`")" && pwd) +fi + +if [ -z $name ] +then + echo "Name must be specified" + echo "" + echo "./cgen.sh [cmake version] [C Standard]" + exit -1 +fi + +if [ -z $cmake_version ] +then + echo "No CMake version supplied...defaulting to 3.15" + cmake_version="3.15" +fi + +if [ -z $c_standard ] +then + echo "No C Standard supplied...defaulting to 11" + c_standard="11" +fi + +mkdir $name +mkdir $name/src +mkdir $name/include + +cmake_config=$name/CMakeLists.txt + +touch $cmake_config + +echo "cmake_minimum_required(VERSION $cmake_version)" >> $cmake_config +echo "project($name)" >> $cmake_config +echo "" >> $cmake_config +echo "set (CMAKE_C_STANDARD $c_standard)" >> $cmake_config +echo "" >> $cmake_config +echo "# Output to bin and lib" >> $cmake_config +echo "set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \${CMAKE_BINARY_DIR}/lib)" >> $cmake_config +echo "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY \${CMAKE_BINARY_DIR}/lib)" >> $cmake_config +echo "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY \${CMAKE_BINARY_DIR}/bin)" >> $cmake_config +echo "" >> $cmake_config +echo "" >> $cmake_config +echo "# The following lines enable compile_commands.json and dump it in the root of the project" >> $cmake_config +echo "# This is needed for clangd and lsp to work" >> $cmake_config +echo "" >> $cmake_config +echo "set(CMAKE_EXPORT_COMPILE_COMMANDS ON)" >> $cmake_config +echo "" >> $cmake_config +echo "if(EXISTS \"\${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json\")" >> $cmake_config +echo " execute_process(COMMAND \${CMAKE_COMMAND} -E copy_if_different" >> $cmake_config +echo " \${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" >> $cmake_config +echo " \${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json" >> $cmake_config +echo " )" >> $cmake_config +echo "endif()" >> $cmake_config +echo "#-------- END COMPILE_COMMANDS.JSON SECTION --------#" >> $cmake_config +echo "" >> $cmake_config +echo "" >> $cmake_config +echo "file(GLOB SOURCE \${PROJECT_SOURCE_DIR}/src/*.c)" >> $cmake_config +echo "file(GLOB INCLUDE \${PROJECT_SOURCE_DIR}/include/*.h)" >> $cmake_config +echo "" >> $cmake_config +echo "" >> $cmake_config +echo "include_directories(\${PROJECT_SOURCE_DIR}/include)" >> $cmake_config +echo "" >> $cmake_config +echo "" >> $cmake_config +echo "# Controls debug printing based on the compiled binary mode - check with #ifdef DEBUG_BUILD" >> $cmake_config +echo "IF(CMAKE_BUILD_TYPE MATCHES DEBUG)" >> $cmake_config +echo "message(\"-- Configuring debug build...\")" >> $cmake_config +echo "add_compile_definitions(DEBUG_BUILD)" >> $cmake_config +echo "ENDIF(CMAKE_BUILD_TYPE MATCHES DEBUG)" >> $cmake_config +echo "" >> $cmake_config +echo "add_executable(\${PROJECT_NAME} \${SOURCE} \${INCLUDE})" >> $cmake_config + + +echo "Copying .clang-format..." +cp $script_dir/cgen_etc/clang_format_template $name/.clang-format + +echo "Copying .gitignore..." +cp $script_dir/cgen_etc/gitignore_template $name/.gitignore + +sample_file=$name/src/main.c + +echo "#include " >> $sample_file +echo "" >> $sample_file +echo "" >> $sample_file +echo "int main(int argc, char **argv)" >> $sample_file +echo "{" >> $sample_file +echo " printf(\"Hello, world!\\n\");" >> $sample_file +echo " return 0;" >> $sample_file +echo "}" >> $sample_file +echo "" >> $sample_file + +git --version 2>&1 >/dev/null +GIT_IS_AVAILABLE=$? + +if [ $GIT_IS_AVAILABLE -eq 0 ] +then + git init $name +fi + +echo "Created project $name." diff --git a/scripts/cgen_etc/clang_format_template b/scripts/cgen_etc/clang_format_template new file mode 100644 index 0000000..db8218d --- /dev/null +++ b/scripts/cgen_etc/clang_format_template @@ -0,0 +1,16 @@ +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: 'true' +AlignConsecutiveAssignments: 'true' +AlignConsecutiveDeclarations: 'true' +AlignTrailingComments: 'true' +BreakBeforeBraces: Allman +IndentCaseLabels: 'true' +IndentWidth: '4' +Language: Cpp +PointerAlignment: Right +SortIncludes: 'true' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'true' +SpaceInEmptyParentheses: 'false' +UseTab: Never +ColumnLimit: 80 \ No newline at end of file diff --git a/scripts/cgen_etc/gitignore_template b/scripts/cgen_etc/gitignore_template new file mode 100644 index 0000000..300d9c4 --- /dev/null +++ b/scripts/cgen_etc/gitignore_template @@ -0,0 +1,97 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# VSCode + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Vim + +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# CMake + +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/zprofile b/zprofile new file mode 100644 index 0000000..6ef54a9 --- /dev/null +++ b/zprofile @@ -0,0 +1,2 @@ +#!/bin/zsh + diff --git a/zshrc b/zshrc new file mode 100644 index 0000000..020d54d --- /dev/null +++ b/zshrc @@ -0,0 +1,230 @@ +#!/bin/zsh + +# ------------------------- +# Resolve this config's real directory +# ------------------------- + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + symlink_dir="$(cd "$(dirname "$(readlink -f "${(%):-%N}")")" && pwd)" +else + # macOS does not ship GNU readlink -f by default. + symlink_dir="$(cd "$(dirname "$(readlink "${(%):-%N}")")" && pwd)" +fi + +# ------------------------- +# Private, machine-local configuration +# ------------------------- +# These files are intentionally not part of this repository. +# Use them for secrets, tokens, private exports, work paths, and host-specific setup. + +[[ -f "$HOME/.bash_private" ]] && source "$HOME/.bash_private" +[[ -f "$HOME/.private" ]] && source "$HOME/.private" +[[ -f "$HOME/.zsh_private" ]] && source "$HOME/.zsh_private" + +# Do not source ~/.inputrc here. +# It is a readline config file, not shell code. + +# ------------------------- +# OS-specific configuration +# ------------------------- + +if [[ "$OSTYPE" == "darwin"* ]]; then + [[ -f "$HOME/.macrc" ]] && source "$HOME/.macrc" +fi + +# ------------------------- +# Local directories +# ------------------------- + +if [[ ! -d "$HOME/projects" ]]; then + echo "Making $HOME/projects" + mkdir -p "$HOME/projects" +fi + +if command -v go >/dev/null 2>&1; then + mkdir -p "$HOME/.go" +fi + +# ------------------------- +# Git completion +# ------------------------- +# Auto-installs Git completion files from a pinned Git commit. +# This avoids fetching from a moving branch like master. +# +# To fill in the placeholders: +# +# cd /tmp +# git clone https://github.com/git/git.git git-src +# cd git-src +# git checkout v2.45.2 +# git rev-parse HEAD +# shasum -a 256 contrib/completion/git-completion.bash +# shasum -a 256 contrib/completion/git-completion.zsh +# +# Then paste the commit and hashes below. + + +ZSH_CACHE_DIR="$HOME/.zsh" +mkdir -p "$ZSH_CACHE_DIR" + +GIT_COMPLETION_COMMIT="bea9ecd24b0c3bf06cab4a851694fe09e7e51408" + +GIT_COMPLETION_BASH_URL="https://raw.githubusercontent.com/git/git/${GIT_COMPLETION_COMMIT}/contrib/completion/git-completion.bash" +GIT_COMPLETION_ZSH_URL="https://raw.githubusercontent.com/git/git/${GIT_COMPLETION_COMMIT}/contrib/completion/git-completion.zsh" + +GIT_COMPLETION_BASH_FILE="$ZSH_CACHE_DIR/git-completion.bash" +GIT_COMPLETION_ZSH_FILE="$ZSH_CACHE_DIR/_git" + +GIT_COMPLETION_BASH_SHA256="e667d8fdc0f0071833c94ccb2565a503bf413da0ae28d74efe2e70e2beb1c4c6" +GIT_COMPLETION_ZSH_SHA256="f6075d283250785edcf09281992bde6b4bcc43eb2dca74488d880a52f7659c09" + +_zshv2_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$1" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + echo "No SHA-256 tool found. Need sha256sum or shasum." >&2 + return 1 + fi +} + +_zshv2_file_has_sha256() { + local file="$1" + local expected_sha="$2" + local actual_sha + + [[ -f "$file" ]] || return 1 + + actual_sha="$(_zshv2_sha256 "$file")" || return 1 + [[ "$actual_sha" == "$expected_sha" ]] +} + +_zshv2_install_verified_file() { + local name="$1" + local url="$2" + local dest="$3" + local expected_sha="$4" + local tmp_file + local actual_sha + + if [[ -z "$url" || -z "$dest" || -z "$expected_sha" ]]; then + echo "Git completion install misconfigured for $name." >&2 + return 1 + fi + + if _zshv2_file_has_sha256 "$dest" "$expected_sha"; then + return 0 + fi + + if ! command -v curl >/dev/null 2>&1; then + echo "curl is required to install $name." >&2 + return 1 + fi + + tmp_file="$(mktemp "${dest}.tmp.XXXXXX")" || return 1 + + if ! curl --fail --location --silent --show-error --output "$tmp_file" "$url"; then + echo "Failed to download $name from $url" >&2 + rm -f "$tmp_file" + return 1 + fi + + actual_sha="$(_zshv2_sha256 "$tmp_file")" || { + rm -f "$tmp_file" + return 1 + } + + if [[ "$actual_sha" != "$expected_sha" ]]; then + echo "SHA-256 mismatch for $name." >&2 + echo "Expected: $expected_sha" >&2 + echo "Actual: $actual_sha" >&2 + rm -f "$tmp_file" + return 1 + fi + + mv "$tmp_file" "$dest" +} + +_zshv2_install_git_completion() { + _zshv2_install_verified_file \ + "git-completion.bash" \ + "$GIT_COMPLETION_BASH_URL" \ + "$GIT_COMPLETION_BASH_FILE" \ + "$GIT_COMPLETION_BASH_SHA256" + + _zshv2_install_verified_file \ + "git-completion.zsh" \ + "$GIT_COMPLETION_ZSH_URL" \ + "$GIT_COMPLETION_ZSH_FILE" \ + "$GIT_COMPLETION_ZSH_SHA256" +} + +_zshv2_install_git_completion + +if [[ -f "$GIT_COMPLETION_BASH_FILE" ]]; then + zstyle ':completion:*:*:git:*' script "$GIT_COMPLETION_BASH_FILE" +fi + +fpath=("$ZSH_CACHE_DIR" $fpath) + +autoload -Uz compinit +compinit + +# ------------------------- +# Pull in modular config +# ------------------------- + +source "$symlink_dir/aliases.zsh" +source "$symlink_dir/functions.zsh" +source "$symlink_dir/exports.zsh" +source "$symlink_dir/initializations.zsh" +source "$symlink_dir/ps1.zsh" +source "$symlink_dir/keybinds.zsh" + +# ------------------------- +# Language/tool initialization +# ------------------------- + +# Rust +[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env" + +# Haskell / GHCup +[[ -f "$HOME/.ghcup/env" ]] && source "$HOME/.ghcup/env" + +# OCaml / opam +[[ -r "$HOME/.opam/opam-init/init.sh" ]] && . "$HOME/.opam/opam-init/init.sh" >/dev/null 2>/dev/null || true + +# ------------------------- +# ZSH syntax highlighting +# ------------------------- +# This still supports an existing local clone under: +# +# $HOME/.zsh/zsh-syntax-highlighting +# +# But it does not automatically clone during shell startup. +# Install with: +# +# brew install zsh-syntax-highlighting +# +# or: +# +# sudo apt install zsh-syntax-highlighting +# +# It must be sourced last. + +if [[ -f "$HOME/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ]]; then + source "$HOME/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +elif [[ -f "/opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ]]; then + source "/opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +elif [[ -f "/usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ]]; then + source "/usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +elif [[ -f "/usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ]]; then + source "/usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" +fi + +# ------------------------- +# Deduplicate PATH +# ------------------------- + +typeset -U path PATH