From 27a8152c257408d6cee3514352ad517d135e69d4 Mon Sep 17 00:00:00 2001 From: Andrejus Date: Thu, 12 Feb 2026 18:13:09 +0000 Subject: [PATCH] fix: codespaces widgets and shortcuts --- README.md | 17 +++++ files/home/.zshrc | 125 +++++++++++++++++++++++++++++++++--- script/install.d/30-mise.sh | 74 ++++++++++++--------- 3 files changed, 177 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index c54b19e..38d5ba9 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,20 @@ by invoking the `setup-new` script directly via `curl`: # Run curl -s https://raw.githubusercontent.com/andrejusk/dotfiles/HEAD/script/setup-new | bash +## Keyboard shortcuts + +Custom zsh widgets bound in `.zshrc`: + +| Key | Mnemonic | Action | +|-----|----------|--------| +| `^B` | **B**ranch | Git branch checkout with log preview | +| `^E` | **E**dit | Find and edit file in `$EDITOR` | +| `^F` | **F**ind | Find in files (rg + fzf), open at line | +| `^G` | **G**o remote | SSH/codespace connect *(local only)* | +| `^J` | **J**ump | Zoxide directory jump | +| `^N` | **N**avigate | Tmux session create/switch | +| `^O` | **O**pen | Open repo/PR/issues/actions in browser | +| `^P` | **P**roject | Switch to workspace project | +| `^S` | **S**ession | Browse & resume Copilot CLI sessions | +| `^Y` | **Y**ank stash | Browse git stashes with diff preview | + diff --git a/files/home/.zshrc b/files/home/.zshrc index 45b74ad..7828930 100644 --- a/files/home/.zshrc +++ b/files/home/.zshrc @@ -74,12 +74,15 @@ _dots_load_history _dots_load_keybindings() { bindkey -e + stty -ixon 2>/dev/null # Ctrl+J: zoxide jump _dots_zoxide_widget() { local result - result="$(zoxide query -i -- 2>&1)" && cd "$result" + result="$(zoxide query -i -- 2>&1)" || { zle reset-prompt; return; } + BUFFER="cd ${(q)result}" zle reset-prompt + zle accept-line } zle -N _dots_zoxide_widget bindkey '^J' _dots_zoxide_widget @@ -90,8 +93,9 @@ _dots_load_keybindings() { branch="$(git branch --all --sort=-committerdate --format='%(refname:short)' 2>/dev/null \ | fzf --preview 'git log --oneline --color -20 {}')" || { zle reset-prompt; return; } branch="${branch#origin/}" - git checkout "$branch" 2>&1 + BUFFER="git checkout ${(q)branch}" zle reset-prompt + zle accept-line } zle -N _dots_git_branch_widget bindkey '^B' _dots_git_branch_widget @@ -99,7 +103,8 @@ _dots_load_keybindings() { # Ctrl+E: edit file _dots_edit_widget() { local file - file="$(fzf --preview 'head -100 {}')" || { zle reset-prompt; return; } + file="$({ rg --files --hidden --glob '!.git' 2>/dev/null || find . -type f -not -path '*/.git/*'; } \ + | fzf --preview 'head -100 {}')" || { zle reset-prompt; return; } ${EDITOR:-vim} "$file" /dev/null ;; - pr) gh pr view --web 2>/dev/null ;; - issues) gh browse --issues 2>/dev/null ;; - actions) gh browse --actions 2>/dev/null ;; + repo) BUFFER="gh browse" ;; + pr) BUFFER="gh pr view --web" ;; + issues) BUFFER="gh browse --issues" ;; + actions) BUFFER="gh browse --actions" ;; esac zle reset-prompt + zle accept-line } zle -N _dots_open_widget bindkey '^O' _dots_open_widget @@ -191,11 +199,112 @@ _dots_load_keybindings() { _dots_project_widget() { local result result="$(zoxide query -l 2>/dev/null | grep "${WORKSPACE:-$HOME/Workspace}" \ - | fzf --preview 'ls -1 {}')" && cd "$result" + | fzf --preview 'ls -1 {}')" || { zle reset-prompt; return; } + BUFFER="cd ${(q)result}" zle reset-prompt + zle accept-line } zle -N _dots_project_widget bindkey '^P' _dots_project_widget + + # Ctrl+S: copilot sessions + _dots_copilot_session_widget() { + local session_dir="$HOME/.copilot/session-state" + [[ -d "$session_dir" ]] || { zle reset-prompt; return; } + local session + session="$(python3 -c " +import os, json, glob +sd = os.path.expanduser('~/.copilot/session-state') +home = os.path.expanduser('~') +entries = [] +for ws in glob.glob(os.path.join(sd, '*/workspace.yaml')): + try: + d = {} + with open(ws) as f: + for l in f: + for k in ('updated_at:','cwd:','id:','summary:','repository:'): + if l.startswith(k): d[k[:-1]] = l.split(': ',1)[1].strip() + sid = d.get('id','') + if not sid: continue + ts = d.get('updated_at','?')[:16] + repo = d.get('repository','').split('/')[-1] if d.get('repository') else '' + summary = d.get('summary','') + msg = '' + ev = os.path.join(os.path.dirname(ws), 'events.jsonl') + if os.path.exists(ev): + with open(ev) as f: + for l in f: + if '\"user.message\"' in l: + try: msg = json.loads(l)['data']['content'].strip().split(chr(10))[0][:60] + except: pass + break + if not msg: continue + ctx = repo or d.get('cwd','?').replace(home,'~') + label = f'{ctx} \u00b7 {summary}' if summary else f'{ctx} \u00b7 {msg}' + entries.append((ts, sid, label)) + except: pass +for jf in glob.glob(os.path.join(sd, '*.jsonl')): + try: + sid = os.path.basename(jf).replace('.jsonl','') + with open(jf) as f: + ts = json.loads(f.readline())['data']['startTime'][:16] + msg = '' + with open(jf) as f: + for l in f: + if '\"user.message\"' in l: + try: msg = json.loads(l)['data']['content'].strip().split(chr(10))[0][:60] + except: pass + break + if not msg: continue + entries.append((ts, sid, msg)) + except: pass +entries.sort(key=lambda x: x[0], reverse=True) +for ts, sid, label in entries: + print(f'{ts} | {sid} | {label}') +" 2>/dev/null | fzf --preview ' + id=$(echo {} | cut -d"|" -f2 | tr -d " ") + sd="'"$session_dir"'" + f="$sd/$id/events.jsonl" + [[ -f "$f" ]] || f="$sd/${id}.jsonl" + [[ -f "$f" ]] || exit 0 + grep "\"user.message\"" "$f" | python3 -c " +import sys,json +for line in sys.stdin: + try: + msg=json.loads(line)[\"data\"][\"content\"].strip().split(chr(10))[0][:100] + print(\">\", msg) + except: pass +" 2>/dev/null + ' --header 'enter=colby | ctrl-r=restricted' \ + --expect=ctrl-r)" || { zle reset-prompt; return; } + local key=$(echo "$session" | head -1) + local line=$(echo "$session" | tail -1) + local id=$(echo "$line" | cut -d'|' -f2 | tr -d ' ') + if [[ "$key" == "ctrl-r" ]]; then + BUFFER="gh copilot --resume $id" + else + BUFFER="copilot --allow-all-tools --allow-all-paths --resume $id" + fi + zle reset-prompt + zle accept-line + } + zle -N _dots_copilot_session_widget + bindkey '^S' _dots_copilot_session_widget + + # Ctrl+Y: git stash browser + _dots_stash_widget() { + local stash + stash="$(git stash list --color=always 2>/dev/null \ + | fzf --ansi --no-sort \ + --preview 'git stash show -p --color=always $(echo {} | cut -d: -f1)' \ + --preview-window='right:60%')" || { zle reset-prompt; return; } + local ref="${stash%%:*}" + BUFFER="git stash apply $ref" + zle reset-prompt + zle accept-line + } + zle -N _dots_stash_widget + bindkey '^Y' _dots_stash_widget } _dots_load_keybindings diff --git a/script/install.d/30-mise.sh b/script/install.d/30-mise.sh index 6eb2643..92574be 100755 --- a/script/install.d/30-mise.sh +++ b/script/install.d/30-mise.sh @@ -6,9 +6,6 @@ # Consolidated installation of Python, Node.js, GitHub CLI, Terraform, Firebase, etc. # -# Skip in Codespaces (use pre-installed versions) -[[ "$DOTS_ENV" == "codespaces" ]] && { log_skip "Codespaces"; return 0; } - # Install mise if ! command -v mise &>/dev/null; then log_info "Installing mise..." @@ -40,53 +37,68 @@ fi mise --version -typeset -a MISE_RUNTIMES=( - "python@3.14.2" - "node@25.5.0" -) +# Skip runtimes in Codespaces (use pre-installed versions) +if [[ "$DOTS_ENV" != "codespaces" ]]; then + typeset -a MISE_RUNTIMES=( + "python@3.14.2" + "node@25.5.0" + ) -log_info "Installing runtimes..." -mise install "${MISE_RUNTIMES[@]}" -for tool in "${MISE_RUNTIMES[@]}"; do - mise use -g "$tool" -done + log_info "Installing runtimes..." + mise install "${MISE_RUNTIMES[@]}" + for tool in "${MISE_RUNTIMES[@]}"; do + mise use -g "$tool" + done +fi # Activate mise shims so runtimes (e.g. python3) are available for app installers eval "$(mise activate bash)" export PATH="$HOME/.local/share/mise/shims:$PATH" typeset -a MISE_APPS=( - "poetry@2.3.2" - "gh@2.86.0" - "terraform@1.14.4" - "firebase@15.5.1" "fzf@latest" "zoxide@latest" "ripgrep@latest" - "fastfetch@latest" ) +if [[ "$DOTS_ENV" != "codespaces" ]]; then + MISE_APPS+=( + "poetry@2.3.2" + "gh@2.86.0" + "terraform@1.14.4" + "firebase@15.5.1" + "fastfetch@latest" + ) +fi + log_info "Installing apps..." mise install "${MISE_APPS[@]}" for tool in "${MISE_APPS[@]}"; do mise use -g "$tool" done -# Setup Poetry ZSH completions (XDG compliant) -COMPLETIONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions" -mkdir -p "$COMPLETIONS_DIR" -if [ ! -f "$COMPLETIONS_DIR/_poetry" ]; then - mise exec -- poetry completions zsh > "$COMPLETIONS_DIR/_poetry" +if [[ "$DOTS_ENV" != "codespaces" ]]; then + # Setup Poetry ZSH completions (XDG compliant) + COMPLETIONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions" + mkdir -p "$COMPLETIONS_DIR" + if [ ! -f "$COMPLETIONS_DIR/_poetry" ]; then + mise exec -- poetry completions zsh > "$COMPLETIONS_DIR/_poetry" + fi fi -# Verify installations using mise exec +# Verify installations log_info "Verifying installations..." -mise exec -- python --version -mise exec -- poetry --version -echo "node $(mise exec -- node --version)" -echo "npm $(mise exec -- npm --version)" -mise exec -- gh --version -mise exec -- terraform --version | head -1 -echo "firebase: $(mise exec -- firebase --version)" -echo "fastfetch: $(mise exec -- fastfetch --version 2>&1 | head -1)" +if [[ "$DOTS_ENV" != "codespaces" ]]; then + mise exec -- python --version + mise exec -- poetry --version + echo "node $(mise exec -- node --version)" + echo "npm $(mise exec -- npm --version)" + mise exec -- gh --version + mise exec -- terraform --version | head -1 + echo "firebase: $(mise exec -- firebase --version)" + echo "fastfetch: $(mise exec -- fastfetch --version 2>&1 | head -1)" +fi +fzf --version +zoxide --version +rg --version | head -1 log_pass "mise tools installed"