diff --git a/bootstrap.sh b/bootstrap.sh index 8f3d57b..d75e871 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -230,7 +230,13 @@ function setupShellEnv { copyFile fish/functions/$f.fish $fishConfigDir/functions/$f.fish done - # Switch users shell to fish: + # zsh: + local zshConfigDir=$configDir/zsh + copyFile zsh/zshenv $zshConfigDir/.zshenv + copyFile zsh/zshrc $zshConfigDir/.zshrc + copyFile zsh/plugins.zsh $zshConfigDir/plugins.zsh + + # Switch user's shell to fish: if ! grep -qi -- "fish" /etc/shells; then echo "Adding fish to /etc/shells" echo $(which fish) | sudo tee -a /etc/shells diff --git a/fish/config.fish b/fish/config.fish index 1bcb947..c890905 100644 --- a/fish/config.fish +++ b/fish/config.fish @@ -47,6 +47,10 @@ end fish_vi_key_bindings +if functions -q nvm + nvm use --silent lts > /dev/null +end + if command -s zoxide > /dev/null zoxide init fish | source function zz @@ -62,9 +66,6 @@ if command -s starship > /dev/null function starship_transient_prompt_func starship module character end - function starship_transient_rprompt_func - starship module time - end starship init fish | source enable_transience end diff --git a/fish/functions/la.fish b/fish/functions/la.fish index 7e170cc..ff5904e 100644 --- a/fish/functions/la.fish +++ b/fish/functions/la.fish @@ -1,3 +1,3 @@ function la - ls -a $argv + ls -a --git $argv end diff --git a/fish/functions/ll.fish b/fish/functions/ll.fish index 6170823..b5f86c2 100644 --- a/fish/functions/ll.fish +++ b/fish/functions/ll.fish @@ -1,3 +1,3 @@ function ll - ls -l $argv + ls -lh --git $argv end diff --git a/starship.toml b/starship.toml index 911ddf7..42639f8 100644 --- a/starship.toml +++ b/starship.toml @@ -15,9 +15,9 @@ $nodejs\ $rust\ $golang\ $docker_context\ -$time\ $cmd_duration\ -\n$character""" +$time\ +$character""" [character] success_symbol = "[[](green) ❯](peach)" diff --git a/zsh/plugins.zsh b/zsh/plugins.zsh new file mode 100644 index 0000000..8310dc3 --- /dev/null +++ b/zsh/plugins.zsh @@ -0,0 +1,27 @@ +# ~/.config/zsh/plugins.zsh + +ZPLUGINDIR="${ZDOTDIR:-$HOME/.config/zsh}/plugins" + +_zplugin_load() { + local plugin_path="${ZPLUGINDIR}/${2}" + if [[ ! -d "$plugin_path" ]]; then + mkdir -p "$ZPLUGINDIR" + echo "Installing ${2}..." + git clone --depth=1 "https://github.com/${1}/${2}" "$plugin_path" \ + || { echo "ERROR: failed to install ${2}" >&2; return 1; } + fi + source "${plugin_path}/${2}.plugin.zsh" +} + +zplugin-update() { + local dir + for dir in "${ZPLUGINDIR}"/*/; do + echo "Updating ${dir:t}..." + git -C "$dir" pull --ff-only + done +} + +_zplugin_load zsh-users zsh-autosuggestions +_zplugin_load zsh-users zsh-history-substring-search +_zplugin_load jeffreytse zsh-vi-mode +_zplugin_load zdharma-continuum fast-syntax-highlighting diff --git a/zsh/zshenv b/zsh/zshenv new file mode 100644 index 0000000..43efab9 --- /dev/null +++ b/zsh/zshenv @@ -0,0 +1,36 @@ +# ~/.config/zsh/.zshenv +# +# to bootstrap zsh to this config, ensure /etc/zshenv has this entry: +# +# if [[ -d "$HOME/.config/zsh" ]] +# then +# export ZDOTDIR="$HOME/.config/zsh" +# fi + +# ---------- XDG base directories ---------- +# Centralizes config/cache/data locations +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_CACHE_HOME="$HOME/.cache" +export XDG_DATA_HOME="$HOME/.local/share" +export XDG_STATE_HOME="$HOME/.local/state" + +export EDITOR="nvim" +export VISUAL="nvim" +export LESS="-c -i -x4 -J -w -M -r" + +if [[ ! -d "$XDG_DATA_HOME/zsh" ]] +then + mkdir -p "$XDG_DATA_HOME/zsh" +fi +if [[ ! -d "$XDG_STATE_HOME/zsh" ]] +then + mkdir -p "$XDG_STATE_HOME/zsh" +fi + +if command -v bat >/dev/null 2>&1; then + export MANPAGER="bat -l man -p" +fi + +export STARSHIP_CONFIG="$XDG_CONFIG_HOME/starship.toml" + +export PATH="$HOME/.local/bin:$PATH" diff --git a/zsh/zshrc b/zsh/zshrc new file mode 100644 index 0000000..b0e3a31 --- /dev/null +++ b/zsh/zshrc @@ -0,0 +1,125 @@ +# ~/.config/zsh/.zshrc + +HISTFILE="$XDG_STATE_HOME/zsh/history" +HISTSIZE=100000 +SAVEHIST=100000 + +setopt APPEND_HISTORY +setopt SHARE_HISTORY +setopt HIST_IGNORE_DUPS +setopt HIST_IGNORE_SPACE +setopt HIST_EXPIRE_DUPS_FIRST +setopt HIST_FIND_NO_DUPS + +setopt AUTOCD +setopt NOBEEP +setopt NUMERIC_GLOB_SORT # sort file10 after file9, not after file1 + +# Initialize zoxide +eval "$(zoxide init zsh)" + +# Load completion system +autoload -Uz compinit +# Ensure completion cache directory exists +mkdir -p -- "$XDG_CACHE_HOME/zsh" +# Initialize completion with cached metadata file +compinit -d "$XDG_CACHE_HOME/zsh/zcompdump" + +zstyle ':completion:*' menu select +# Make completion case-insensitive +zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' # lowercase input matches upper and lower + +alias l='bat' +alias ls='eza --sort ext --group-directories-first --classify --icons' +alias ll='eza -lh --sort ext --group-directories-first --classify --icons --git' +alias la='eza -lah --sort ext --group-directories-first --classify --icons --git' +alias tree='eza --tree --sort ext --group-directories-first --classify --icons' +# Reuse ls completions for eza (avoids defining a separate completion function) +compdef eza=ls + +alias cat='bat' +alias grep='rg --color=auto' +alias diff='diff --color=auto' +alias df='df -h' + +alias -- -='cd -' # -- prevents - being parsed as a flag; cd - jumps to previous directory + +alias v='nvim' +alias vi='nvim' +alias vim='nvim' + +## FZF Configuration +export FZF_DEFAULT_COMMAND='fd --type f --hidden --strip-cwd-prefix' # strip-cwd-prefix removes the leading ./ from results +# Ctrl-T uses fd +export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" + +export FZF_DEFAULT_OPTS=' + --height=60% + --layout=reverse + --border=rounded + --prompt=" " + --pointer=" " + --preview-window=right:65%:wrap:border-left +' + +export _FZF_PREVIEW_CMD='bat --color=always --style=plain,numbers --line-range=:500 {}' +export FZF_CTRL_T_OPTS="--preview '$_FZF_PREVIEW_CMD'" + +# Ctrl+F: file picker excluding hidden files +_fzf_file_no_hidden() { + local cmd result + cmd="${FZF_DEFAULT_COMMAND/--hidden /}" + result=$(eval "${cmd:-find . -type f}" | fzf --preview "$_FZF_PREVIEW_CMD") \ + && LBUFFER+="$result" # LBUFFER is the text left of the cursor + zle reset-prompt +} +zle -N _fzf_file_no_hidden + +# macOS / Homebrew (Apple Silicon) +if [[ -f /opt/homebrew/opt/fzf/shell/key-bindings.zsh ]]; then + source /opt/homebrew/opt/fzf/shell/key-bindings.zsh + source /opt/homebrew/opt/fzf/shell/completion.zsh +fi + +# Arch +if [[ -f /usr/share/fzf/key-bindings.zsh ]]; then + source /usr/share/fzf/key-bindings.zsh + source /usr/share/fzf/completion.zsh +fi + +# Ubuntu +if [[ -f /usr/share/doc/fzf/examples/key-bindings.zsh ]]; then + source /usr/share/doc/fzf/examples/key-bindings.zsh + source /usr/share/doc/fzf/examples/completion.zsh +fi + +# Cursor shape per vi mode +ZVM_INSERT_MODE_CURSOR=$ZVM_CURSOR_BEAM +ZVM_NORMAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK +ZVM_VISUAL_MODE_CURSOR=$ZVM_CURSOR_BLOCK + +ZVM_VI_HIGHLIGHT_BACKGROUND=none +ZVM_VI_HIGHLIGHT_FOREGROUND=none +ZVM_VI_HIGHLIGHT_EXTRASTYLE=none + +# zsh-vi-mode resets all bindings on init, so custom bindings +# must be registered via this hook to survive. +zvm_after_init() { + # Ctrl+F -> fzf file picker (no hidden files) + bindkey '^F' _fzf_file_no_hidden + + # Ctrl+\ -> toggle autosuggestions (useful for screen recordings) + bindkey '^\' autosuggest-toggle + + bindkey '^[[A' history-substring-search-up + bindkey '^[[B' history-substring-search-down +} + +# Plugins and plugin manager +source "$ZDOTDIR/plugins.zsh" + +if [[ -f "$HOME/.cargo/env" ]]; then + source "$HOME/.cargo/env" +fi + +eval "$(starship init zsh)"