-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathzshrc
More file actions
364 lines (311 loc) · 14.5 KB
/
Copy pathzshrc
File metadata and controls
364 lines (311 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# =============================================================================
# zshrc — Zsh configuration
# Managed by dotfiles: https://github.com/imaqsood/dotfiles
# =============================================================================
# -- Powerlevel10k instant prompt (must be near top) --------------------------
# quiet: suppresses warnings and avoids garbled lines on terminal resize
typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
# -- Oh My Zsh ----------------------------------------------------------------
export ZSH=$HOME/.oh-my-zsh
export ZSH_THEME="powerlevel10k/powerlevel10k"
plugins=(git tmux docker fzf ruby docker-compose)
source $ZSH/oh-my-zsh.sh
# -- Vi mode ------------------------------------------------------------------
bindkey -v
# -- Zsh plugins (direct source — ~900ms faster than zplug) -------------------
# Plugins are pre-installed in ~/.zplug/repos/. To install new ones:
# git clone <repo-url> ~/.zplug/repos/<author>/<name>
# NOTE: p10k and oh-my-zsh plugins are loaded above via ZSH_THEME + plugins=()
export ZPLUG_HOME=$HOME/.zplug
[[ -f $ZPLUG_HOME/repos/zsh-users/zsh-autosuggestions/zsh-autosuggestions.zsh ]] && \
source $ZPLUG_HOME/repos/zsh-users/zsh-autosuggestions/zsh-autosuggestions.zsh
[[ -f $ZPLUG_HOME/repos/zsh-users/zsh-history-substring-search/zsh-history-substring-search.zsh ]] && \
source $ZPLUG_HOME/repos/zsh-users/zsh-history-substring-search/zsh-history-substring-search.zsh
[[ -f $ZPLUG_HOME/repos/chrissicool/zsh-256color/zsh-256color.plugin.zsh ]] && \
source $ZPLUG_HOME/repos/chrissicool/zsh-256color/zsh-256color.plugin.zsh
[[ -f $ZPLUG_HOME/repos/loiccoyle/zsh-github-copilot/zsh-github-copilot.plugin.zsh ]] && \
source $ZPLUG_HOME/repos/loiccoyle/zsh-github-copilot/zsh-github-copilot.plugin.zsh
# syntax-highlighting must be sourced LAST (after all widgets are defined)
[[ -f $ZPLUG_HOME/repos/zsh-users/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]] && \
source $ZPLUG_HOME/repos/zsh-users/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
# -- FZF configuration -------------------------------------------------------
export FZF_DEFAULT_COMMAND="fd --type f --hidden --follow --exclude .git"
export FZF_DEFAULT_OPTS='--height 40% --tmux bottom,40% --layout reverse --border top'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND="fd --type d --hidden --follow --exclude .git"
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
# -- GitHub Copilot keybindings -----------------------------------------------
bindkey '»' zsh_gh_copilot_explain # Option+Shift+\ to explain
bindkey '«' zsh_gh_copilot_suggest # Option+\ to suggest
# -- Modern CLI aliases (next-gen replacements) --------------------------------
if command -v eza &> /dev/null; then
alias l="eza --icons --git"
alias ls="eza --icons"
alias ll="eza -la --icons --git --header"
alias lt="eza --tree --icons --git --level=3"
alias la="eza -la --icons --git"
elif command -v exa &> /dev/null; then
alias l="exa --icons --git"
alias ls="exa --icons"
alias ll="exa -la --icons --git"
else
alias l="ls -lah"
fi
alias cat="bat --paging=never 2>/dev/null || cat"
alias cop="git add -N .; git diff --name-only | xargs rubocop $@"
# -- Git aliases ---------------------------------------------------------------
alias gs="git status -sb"
alias gd="git diff"
alias gds="git diff --staged"
alias gl="git log --oneline --graph --decorate -20"
alias gsa="gstash"
# -- Lazygit / Lazydocker shortcuts -------------------------------------------
alias lg="lazygit"
alias ldk="lazydocker"
# -- Navigation (zoxide) ------------------------------------------------------
if command -v zoxide &> /dev/null; then
eval "$(zoxide init zsh)"
fi
# -- Shell history (atuin) ----------------------------------------------------
if command -v atuin &> /dev/null; then
eval "$(atuin init zsh)"
fi
# -- Version manager (mise — replaces pyenv, rbenv, nvm, sdkman) --------------
#if command -v mise &> /dev/null; then
# eval "$(mise activate zsh)"
#fi
# -- Legacy version managers (loaded alongside mise until full migration) ------
# rbenv (still managing your Ruby versions)
if [[ -d $HOME/.rbenv ]]; then
eval "$(rbenv init - zsh)"
fi
# rvm
if [[ -d $HOME/.rvm ]]; then
export PATH="$PATH:$HOME/.rvm/bin"
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
fi
# pyenv (lazy-loaded — saves ~575ms startup time)
if [[ -d $HOME/.pyenv ]]; then
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
export PATH="$PYENV_ROOT/shims:$PATH"
pyenv() { unset -f pyenv; eval "$(command pyenv init - zsh)"; pyenv "$@"; }
fi
# nvm (lazy-loaded — saves ~350ms startup time)
export NVM_DIR="$HOME/.nvm"
if [[ -s "$NVM_DIR/nvm.sh" ]]; then
# Lazy load: create placeholder functions that load nvm on first use
nvm() { unset -f nvm node npm npx; \. "$NVM_DIR/nvm.sh"; [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"; nvm "$@"; }
node() { unset -f nvm node npm npx; \. "$NVM_DIR/nvm.sh"; node "$@"; }
npm() { unset -f nvm node npm npx; \. "$NVM_DIR/nvm.sh"; npm "$@"; }
npx() { unset -f nvm node npm npx; \. "$NVM_DIR/nvm.sh"; npx "$@"; }
fi
# sdkman (lazy-loaded — avoids network timeout on startup)
export SDKMAN_DIR="$HOME/.sdkman"
if [[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then
sdk() { unset -f sdk; source "$SDKMAN_DIR/bin/sdkman-init.sh"; sdk "$@"; }
fi
# -- GitHub Copilot CLI -------------------------------------------------------
# Wrapped in braces to suppress all output (p10k instant prompt compatibility)
if command -v gh &> /dev/null; then
if gh extension list 2>&1 | grep -q copilot 2>/dev/null; then
eval "$(gh copilot alias -- zsh 2>/dev/null)"
fi
fi
# -- Claude Code account switcher ---------------------------------------------
# Two accounts share one machine via separate config dirs (each keeps its own
# credentials/history/projects); shared config is symlinked in by
# bin/setup-claude.sh. Default is "work". Persist a choice with `claude-use -s`.
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
[[ -f "$HOME/.config/claude-account" ]] && \
export CLAUDE_CONFIG_DIR="$(<"$HOME/.config/claude-account")"
claude-use() {
local persist=0 acct=""
[[ "$1" == "-s" || "$1" == "--save" ]] && { persist=1; shift; }
acct="$1"
case "$acct" in
work) export CLAUDE_CONFIG_DIR="$HOME/.claude" ;;
personal) export CLAUDE_CONFIG_DIR="$HOME/.claude-personal" ;;
""|status)
local cur="work"
[[ "$CLAUDE_CONFIG_DIR" == "$HOME/.claude-personal" ]] && cur="personal"
echo "claude account: $cur ($CLAUDE_CONFIG_DIR)"
return 0 ;;
*) echo "usage: claude-use [-s] work|personal|status" >&2; return 1 ;;
esac
if (( persist )); then
mkdir -p "$HOME/.config"
print -r -- "$CLAUDE_CONFIG_DIR" > "$HOME/.config/claude-account"
fi
echo "claude account: $acct ($CLAUDE_CONFIG_DIR)"
}
# -- git worktree + tmux session helper ---------------------------------------
# Run parallel Claude sessions, one per task, each in its own worktree+session.
# gwt <branch> [base] create/reuse a worktree for <branch> (off base, default
# HEAD) and open a tmux/sesh session rooted there
# gwt fzf-pick an existing worktree and jump to it
# gwt-rm <branch> remove a worktree and prune
# Worktrees live under $WORKTREE_HOME/<repo>/<branch> (default ~/dev/worktrees).
export WORKTREE_HOME="${WORKTREE_HOME:-$HOME/dev/worktrees}"
_gwt_session() {
local dir="$1" name
name=$(basename "$dir" | tr '.:' '__')
if command -v sesh &> /dev/null && [[ -n "$TMUX" ]]; then
sesh connect "$dir"
elif command -v tmux &> /dev/null && [[ -n "$TMUX" ]]; then
tmux new-session -d -s "$name" -c "$dir" 2>/dev/null
tmux switch-client -t "$name"
elif command -v tmux &> /dev/null; then
tmux new-session -A -s "$name" -c "$dir"
else
cd "$dir"
fi
}
# Repo name from the *common* git dir, so it's stable whether you're in the
# main checkout or inside a worktree.
_gwt_repo() {
local common; common=$(git rev-parse --git-common-dir 2>/dev/null) || return 1
[[ "$common" = /* ]] || common="$PWD/$common"
echo "${${common:h}:t}"
}
# oh-my-zsh's git plugin aliases `gwt` to `git worktree`; drop it so we can
# define our richer helper (zsh can't define a function over an existing alias).
unalias gwt 2>/dev/null
gwt() {
local repo; repo=$(_gwt_repo) || { echo "gwt: not in a git repo" >&2; return 1; }
if [[ -z "$1" ]]; then
command -v fzf &> /dev/null || { echo "gwt: need a branch arg (or fzf)" >&2; return 1; }
local sel; sel=$(git worktree list | fzf --reverse --prompt="worktree ") || return
_gwt_session "${sel%% *}"
return
fi
local branch="$1" base="${2:-HEAD}"
local dir="$WORKTREE_HOME/$repo/${branch//\//-}"
if [[ ! -d "$dir" ]]; then
mkdir -p "${dir:h}"
if git show-ref --verify --quiet "refs/heads/$branch"; then
git worktree add "$dir" "$branch" || return
else
git worktree add -b "$branch" "$dir" "$base" || return
fi
fi
_gwt_session "$dir"
}
gwt-rm() {
[[ -z "$1" ]] && { echo "usage: gwt-rm <branch>" >&2; return 1; }
# Find the worktree dir registered for this branch (robust from anywhere).
local dir; dir=$(git worktree list --porcelain 2>/dev/null \
| awk -v b="refs/heads/$1" '/^worktree /{w=substr($0,10)} /^branch /{if($2==b){print w; exit}}')
[[ -z "$dir" ]] && { echo "gwt-rm: no worktree for branch '$1'" >&2; return 1; }
git worktree remove "$dir" && echo "removed worktree: $1 ($dir)"
git worktree prune
}
# -- Powerlevel10k config -----------------------------------------------------
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
# -- PATH additions -----------------------------------------------------------
# Homebrew (Apple Silicon)
if [[ -d /opt/homebrew ]]; then
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH"
export PATH="/opt/homebrew/opt/curl/bin:$PATH"
fi
# Python (Homebrew)
if command -v brew &> /dev/null; then
BREW_PYTHON="$(brew --prefix python 2>/dev/null)/libexec/bin"
[[ -d "$BREW_PYTHON" ]] && export PATH="$BREW_PYTHON:$PATH"
fi
export PATH="$HOME/.local/bin:$PATH"
export PATH="$HOME/dev:$PATH"
# LM Studio CLI
[[ -d "$HOME/.lmstudio/bin" ]] && export PATH="$PATH:$HOME/.lmstudio/bin"
# -- Environment variables ----------------------------------------------------
export GIT_EDITOR=nvim
export EDITOR=nvim
export VISUAL=nvim
export BUNDLE_PATH=.bundle/gem
export BUNDLE_BIN=.bundle/bin
# Source private env files (not in git)
[[ ! -f ~/.env ]] || source ~/.env
[[ -s "$HOME/.env-ccm" ]] && source "$HOME/.env-ccm"
# -- Tmux sessions helper -----------------------------------------------------
alias create_tmux_sessions='tmux has-session -t puppet 2>/dev/null || tmux new-session -d -s puppet -c ~/dev/puppet; \
tmux has-session -t modules 2>/dev/null || tmux new-session -d -s module -c ~/dev/puppet/modules; \
tmux has-session -t action 2>/dev/null || tmux new-session -d -s action -c ~/dev/action-project; \
tmux has-session -t p4costy 2>/dev/null || { \
tmux new-session -d -s p4costy; \
tmux new-window -t p4costy:2 -n ccm-backend -c ~/dev/p4costy/ccm-backend; \
tmux new-window -t p4costy:3 -n ccm-frontend -c ~/dev/p4costy/ccm-frontend; \
tmux new-window -t p4costy:4 -n ccm-helm -c ~/dev/p4costy/ccm-helm; \
tmux new-window -t p4costy:5 -n ccm-infra -c ~/dev/p4costy/ccm-infra; \
tmux new-window -t p4costy:6 -n ccm-k8s-configs -c ~/dev/p4costy/ccm-k8s-configs; \
}; \
tmux has-session -t dev 2>/dev/null || tmux new-session -d -s dev -c ~/dev; \
tmux attach'
# -- Git stash fuzzy finder ----------------------------------------------------
function gstash() {
local msg="$1"
local stashes=$(git stash list --format='%gd: %s')
if [[ -z "$msg" ]]; then
if command -v fzf &> /dev/null; then
local selected_stash=$(echo "$stashes" | fzf --prompt="Select stash to apply: ")
if [[ -n "$selected_stash" ]]; then
local stash_name=$(echo "$selected_stash" | awk -F: '{print $1}')
git stash apply "$stash_name"
else
echo "No stash selected."
fi
return 0
else
echo "Multiple stashes found, but fzf is not installed."
return 1
fi
fi
local exact_match=$(echo "$stashes" | grep -E "stash@\{\d+\}: On [^:]+: $msg$")
local match_count=$(echo "$exact_match" | sed '/^$/d' | wc -l | tr -d ' ')
if [[ "$match_count" -eq 1 ]]; then
local stash_name=$(echo "$exact_match" | awk -F: '{print $1}')
echo "Applying stash: $exact_match"
git stash apply "$stash_name"
return 0
fi
local matches=$(echo "$stashes" | grep -i "$msg")
if [[ -z "$matches" ]]; then
echo "No stash found matching: $msg"
return 1
fi
if [[ $(echo "$matches" | wc -l) -eq 1 ]]; then
echo "Found stash: $matches"
read -q "choice?Apply this stash? (y/n) "
echo
if [[ "$choice" =~ ^[Yy]$ ]]; then
local stash_name=$(echo "$matches" | awk -F: '{print $1}')
git stash apply "$stash_name"
else
echo "Operation canceled."
fi
return 0
fi
if command -v fzf &> /dev/null; then
local selected_stash=$(echo "$matches" | fzf --prompt="Select stash to apply: ")
if [[ -n "$selected_stash" ]]; then
local stash_name=$(echo "$selected_stash" | awk -F: '{print $1}')
git stash apply "$stash_name"
else
echo "No stash selected."
fi
else
echo "Multiple stashes found, but fzf is not installed."
return 1
fi
}
# -- Docker CLI completions ---------------------------------------------------
# NOTE: fpath must be set BEFORE oh-my-zsh sources compinit.
# Docker completions are handled by adding to fpath above oh-my-zsh,
# or by running compinit only if it hasn't been loaded yet.
if [[ -d "$HOME/.docker/completions" ]]; then
fpath=($HOME/.docker/completions $fpath)
# Only reload compinit if docker completions dir exists and compinit already ran
autoload -Uz compinit && compinit -C # -C skips security check (fast)
fi