fix: codespaces widgets and shortcuts
This commit is contained in:
17
README.md
17
README.md
@@ -27,3 +27,20 @@ by invoking the `setup-new` script directly via `curl`:
|
|||||||
# Run
|
# Run
|
||||||
curl -s https://raw.githubusercontent.com/andrejusk/dotfiles/HEAD/script/setup-new | bash
|
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 |
|
||||||
|
|
||||||
|
|||||||
@@ -74,12 +74,15 @@ _dots_load_history
|
|||||||
|
|
||||||
_dots_load_keybindings() {
|
_dots_load_keybindings() {
|
||||||
bindkey -e
|
bindkey -e
|
||||||
|
stty -ixon 2>/dev/null
|
||||||
|
|
||||||
# Ctrl+J: zoxide jump
|
# Ctrl+J: zoxide jump
|
||||||
_dots_zoxide_widget() {
|
_dots_zoxide_widget() {
|
||||||
local result
|
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 reset-prompt
|
||||||
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N _dots_zoxide_widget
|
zle -N _dots_zoxide_widget
|
||||||
bindkey '^J' _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 \
|
branch="$(git branch --all --sort=-committerdate --format='%(refname:short)' 2>/dev/null \
|
||||||
| fzf --preview 'git log --oneline --color -20 {}')" || { zle reset-prompt; return; }
|
| fzf --preview 'git log --oneline --color -20 {}')" || { zle reset-prompt; return; }
|
||||||
branch="${branch#origin/}"
|
branch="${branch#origin/}"
|
||||||
git checkout "$branch" 2>&1
|
BUFFER="git checkout ${(q)branch}"
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N _dots_git_branch_widget
|
zle -N _dots_git_branch_widget
|
||||||
bindkey '^B' _dots_git_branch_widget
|
bindkey '^B' _dots_git_branch_widget
|
||||||
@@ -99,7 +103,8 @@ _dots_load_keybindings() {
|
|||||||
# Ctrl+E: edit file
|
# Ctrl+E: edit file
|
||||||
_dots_edit_widget() {
|
_dots_edit_widget() {
|
||||||
local file
|
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/tty
|
${EDITOR:-vim} "$file" </dev/tty
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
}
|
}
|
||||||
@@ -139,6 +144,8 @@ _dots_load_keybindings() {
|
|||||||
}
|
}
|
||||||
zle -N _dots_ssh_widget
|
zle -N _dots_ssh_widget
|
||||||
bindkey '^G' _dots_ssh_widget
|
bindkey '^G' _dots_ssh_widget
|
||||||
|
else
|
||||||
|
bindkey -r '^G'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ctrl+F: find in files
|
# Ctrl+F: find in files
|
||||||
@@ -177,12 +184,13 @@ _dots_load_keybindings() {
|
|||||||
local choice
|
local choice
|
||||||
choice="$(printf 'repo\npr\nissues\nactions' | fzf)" || { zle reset-prompt; return; }
|
choice="$(printf 'repo\npr\nissues\nactions' | fzf)" || { zle reset-prompt; return; }
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
repo) gh browse 2>/dev/null ;;
|
repo) BUFFER="gh browse" ;;
|
||||||
pr) gh pr view --web 2>/dev/null ;;
|
pr) BUFFER="gh pr view --web" ;;
|
||||||
issues) gh browse --issues 2>/dev/null ;;
|
issues) BUFFER="gh browse --issues" ;;
|
||||||
actions) gh browse --actions 2>/dev/null ;;
|
actions) BUFFER="gh browse --actions" ;;
|
||||||
esac
|
esac
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N _dots_open_widget
|
zle -N _dots_open_widget
|
||||||
bindkey '^O' _dots_open_widget
|
bindkey '^O' _dots_open_widget
|
||||||
@@ -191,11 +199,112 @@ _dots_load_keybindings() {
|
|||||||
_dots_project_widget() {
|
_dots_project_widget() {
|
||||||
local result
|
local result
|
||||||
result="$(zoxide query -l 2>/dev/null | grep "${WORKSPACE:-$HOME/Workspace}" \
|
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 reset-prompt
|
||||||
|
zle accept-line
|
||||||
}
|
}
|
||||||
zle -N _dots_project_widget
|
zle -N _dots_project_widget
|
||||||
bindkey '^P' _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
|
_dots_load_keybindings
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
# Consolidated installation of Python, Node.js, GitHub CLI, Terraform, Firebase, etc.
|
# 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
|
# Install mise
|
||||||
if ! command -v mise &>/dev/null; then
|
if ! command -v mise &>/dev/null; then
|
||||||
log_info "Installing mise..."
|
log_info "Installing mise..."
|
||||||
@@ -40,6 +37,8 @@ fi
|
|||||||
|
|
||||||
mise --version
|
mise --version
|
||||||
|
|
||||||
|
# Skip runtimes in Codespaces (use pre-installed versions)
|
||||||
|
if [[ "$DOTS_ENV" != "codespaces" ]]; then
|
||||||
typeset -a MISE_RUNTIMES=(
|
typeset -a MISE_RUNTIMES=(
|
||||||
"python@3.14.2"
|
"python@3.14.2"
|
||||||
"node@25.5.0"
|
"node@25.5.0"
|
||||||
@@ -50,21 +49,27 @@ mise install "${MISE_RUNTIMES[@]}"
|
|||||||
for tool in "${MISE_RUNTIMES[@]}"; do
|
for tool in "${MISE_RUNTIMES[@]}"; do
|
||||||
mise use -g "$tool"
|
mise use -g "$tool"
|
||||||
done
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# Activate mise shims so runtimes (e.g. python3) are available for app installers
|
# Activate mise shims so runtimes (e.g. python3) are available for app installers
|
||||||
eval "$(mise activate bash)"
|
eval "$(mise activate bash)"
|
||||||
export PATH="$HOME/.local/share/mise/shims:$PATH"
|
export PATH="$HOME/.local/share/mise/shims:$PATH"
|
||||||
|
|
||||||
typeset -a MISE_APPS=(
|
typeset -a MISE_APPS=(
|
||||||
|
"fzf@latest"
|
||||||
|
"zoxide@latest"
|
||||||
|
"ripgrep@latest"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "$DOTS_ENV" != "codespaces" ]]; then
|
||||||
|
MISE_APPS+=(
|
||||||
"poetry@2.3.2"
|
"poetry@2.3.2"
|
||||||
"gh@2.86.0"
|
"gh@2.86.0"
|
||||||
"terraform@1.14.4"
|
"terraform@1.14.4"
|
||||||
"firebase@15.5.1"
|
"firebase@15.5.1"
|
||||||
"fzf@latest"
|
|
||||||
"zoxide@latest"
|
|
||||||
"ripgrep@latest"
|
|
||||||
"fastfetch@latest"
|
"fastfetch@latest"
|
||||||
)
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
log_info "Installing apps..."
|
log_info "Installing apps..."
|
||||||
mise install "${MISE_APPS[@]}"
|
mise install "${MISE_APPS[@]}"
|
||||||
@@ -72,15 +77,18 @@ for tool in "${MISE_APPS[@]}"; do
|
|||||||
mise use -g "$tool"
|
mise use -g "$tool"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [[ "$DOTS_ENV" != "codespaces" ]]; then
|
||||||
# Setup Poetry ZSH completions (XDG compliant)
|
# Setup Poetry ZSH completions (XDG compliant)
|
||||||
COMPLETIONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions"
|
COMPLETIONS_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions"
|
||||||
mkdir -p "$COMPLETIONS_DIR"
|
mkdir -p "$COMPLETIONS_DIR"
|
||||||
if [ ! -f "$COMPLETIONS_DIR/_poetry" ]; then
|
if [ ! -f "$COMPLETIONS_DIR/_poetry" ]; then
|
||||||
mise exec -- poetry completions zsh > "$COMPLETIONS_DIR/_poetry"
|
mise exec -- poetry completions zsh > "$COMPLETIONS_DIR/_poetry"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Verify installations using mise exec
|
# Verify installations
|
||||||
log_info "Verifying installations..."
|
log_info "Verifying installations..."
|
||||||
|
if [[ "$DOTS_ENV" != "codespaces" ]]; then
|
||||||
mise exec -- python --version
|
mise exec -- python --version
|
||||||
mise exec -- poetry --version
|
mise exec -- poetry --version
|
||||||
echo "node $(mise exec -- node --version)"
|
echo "node $(mise exec -- node --version)"
|
||||||
@@ -89,4 +97,8 @@ mise exec -- gh --version
|
|||||||
mise exec -- terraform --version | head -1
|
mise exec -- terraform --version | head -1
|
||||||
echo "firebase: $(mise exec -- firebase --version)"
|
echo "firebase: $(mise exec -- firebase --version)"
|
||||||
echo "fastfetch: $(mise exec -- fastfetch --version 2>&1 | head -1)"
|
echo "fastfetch: $(mise exec -- fastfetch --version 2>&1 | head -1)"
|
||||||
|
fi
|
||||||
|
fzf --version
|
||||||
|
zoxide --version
|
||||||
|
rg --version | head -1
|
||||||
log_pass "mise tools installed"
|
log_pass "mise tools installed"
|
||||||
|
|||||||
Reference in New Issue
Block a user