feat: tmux and widgets
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ temp
|
||||
# ssh env
|
||||
**/id_*
|
||||
**/known_hosts*
|
||||
**/config.d
|
||||
|
||||
# setup files
|
||||
**/autoload
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
Include config.d/*
|
||||
|
||||
Host *
|
||||
IgnoreUnknown UseKeychain
|
||||
UseKeychain yes
|
||||
|
||||
@@ -19,3 +19,40 @@ alias gl='git ld'
|
||||
alias ga='git a'
|
||||
alias gr='git r'
|
||||
|
||||
# fzf workflows
|
||||
gb() { local b; b="$(git branch --all --sort=-committerdate --format='%(refname:short)' | fzf --preview 'git log --oneline --color -20 {}')" && git checkout "${b#origin/}"; }
|
||||
glo() { git log --oneline --color --decorate | fzf --ansi --preview 'git show --color --stat {1}' --bind 'enter:become(echo {1})'; }
|
||||
f() { local f; f="$(fzf --preview 'head -100 {}')" && ${EDITOR:-vim} "$f"; }
|
||||
|
||||
ssh() {
|
||||
local ssh_target="${@: -1}"
|
||||
if [[ -n "$TMUX" ]]; then
|
||||
tmux rename-window "ssh:$ssh_target"
|
||||
fi
|
||||
|
||||
command ssh "$@"
|
||||
local rc=$?
|
||||
|
||||
if (( rc == 0 )); then
|
||||
local host="$ssh_target"
|
||||
local ssh_log="${XDG_DATA_HOME:-$HOME/.local/share}/ssh/log"
|
||||
mkdir -p "$(dirname "$ssh_log")"
|
||||
|
||||
if [[ "$ssh_target" == *@* ]]; then
|
||||
local user="${ssh_target%%@*}"
|
||||
host="${ssh_target#*@}"
|
||||
local config_file="$HOME/.ssh/config.d/auto"
|
||||
mkdir -p "$HOME/.ssh/config.d"
|
||||
if ! grep -q "^Host $host$" "$config_file" 2>/dev/null; then
|
||||
printf '\nHost %s\n HostName %s\n User %s\n' "$host" "$host" "$user" >> "$config_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '%s %s\n' "$(date +%s)" "$host" >> "$ssh_log"
|
||||
fi
|
||||
|
||||
[[ -n "$TMUX" ]] && tmux set automatic-rename on
|
||||
return $rc
|
||||
}
|
||||
s() { local h; h="$(_dots_ssh_hosts | fzf)" && ssh "$h"; }
|
||||
|
||||
|
||||
66
files/home/.tmux.conf
Normal file
66
files/home/.tmux.conf
Normal file
@@ -0,0 +1,66 @@
|
||||
# Prefix: Ctrl+A
|
||||
unbind C-b
|
||||
set -g prefix C-a
|
||||
bind C-a send-prefix
|
||||
|
||||
# True color
|
||||
set -g default-terminal "tmux-256color"
|
||||
set -ag terminal-overrides ",xterm-256color:RGB"
|
||||
|
||||
# Shell
|
||||
set -g default-shell "$SHELL"
|
||||
|
||||
# Windows and panes start at 1
|
||||
set -g base-index 1
|
||||
setw -g pane-base-index 1
|
||||
set -g renumber-windows on
|
||||
set -g history-limit 50000
|
||||
set -sg escape-time 10
|
||||
set -g focus-events on
|
||||
|
||||
# Copy mode
|
||||
setw -g mode-keys vi
|
||||
bind -T copy-mode-vi v send -X begin-selection
|
||||
bind -T copy-mode-vi y send -X copy-pipe-and-cancel "pbcopy 2>/dev/null || xclip -selection clipboard 2>/dev/null || xsel --clipboard"
|
||||
|
||||
# Splits
|
||||
bind | split-window -h -c "#{pane_current_path}"
|
||||
bind - split-window -v -c "#{pane_current_path}"
|
||||
unbind '"'
|
||||
unbind %
|
||||
|
||||
bind c new-window -c "#{pane_current_path}"
|
||||
|
||||
# Pane navigation
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
|
||||
# Pane resize
|
||||
bind -r C-h resize-pane -L 5
|
||||
bind -r C-j resize-pane -D 5
|
||||
bind -r C-k resize-pane -U 5
|
||||
bind -r C-l resize-pane -R 5
|
||||
|
||||
# Reload
|
||||
bind r source-file ~/.tmux.conf \; display "Config reloaded"
|
||||
|
||||
# Status bar
|
||||
set -g status-position bottom
|
||||
set -g status-interval 5
|
||||
set -g status-style "fg=#728cb8,bg=default"
|
||||
set -g status-left ""
|
||||
set -g status-right ""
|
||||
set -g window-status-format " #I:#W "
|
||||
set -g window-status-current-format "#[fg=#2cb494,bold] #I:#W "
|
||||
set -g window-status-bell-style "fg=#f88c14,bold"
|
||||
set -g pane-border-style "fg=#728cb8"
|
||||
set -g pane-active-border-style "fg=#2cb494"
|
||||
set -g message-style "fg=#2cb494,bg=default"
|
||||
set -g message-command-style "fg=#f88c14,bg=default"
|
||||
set -g mode-style "fg=default,bg=#728cb8,bold"
|
||||
|
||||
# Auto-rename
|
||||
set -g automatic-rename on
|
||||
set -g automatic-rename-format '#{?#{==:#{pane_current_command},zsh},zsh:#{b:pane_current_path},#{pane_current_command}}'
|
||||
@@ -32,15 +32,21 @@ _dots_cache_ls_colors
|
||||
[[ -f ~/.aliases ]] && source ~/.aliases
|
||||
|
||||
_dots_init_completion() {
|
||||
# Add custom completions to fpath
|
||||
local comp_dir="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions"
|
||||
[[ -d "$comp_dir" ]] && fpath=("$comp_dir" $fpath)
|
||||
|
||||
autoload -Uz compinit
|
||||
# Rebuild zcompdump at most once per day
|
||||
# Daily cache invalidation
|
||||
local dump="$HOME/.zcompdump"
|
||||
if [[ -f "$dump" && $(date +'%j') == $(stat -f '%Sm' -t '%j' "$dump" 2>/dev/null || date -r "$dump" +'%j' 2>/dev/null) ]]; then
|
||||
compinit -C
|
||||
if [[ -f "$dump" ]]; then
|
||||
zmodload -F zsh/stat b:zstat 2>/dev/null
|
||||
local -a dump_stat
|
||||
zstat -A dump_stat +mtime "$dump" 2>/dev/null
|
||||
if (( dump_stat[1] > EPOCHSECONDS - 86400 )); then
|
||||
compinit -C
|
||||
else
|
||||
compinit
|
||||
fi
|
||||
else
|
||||
compinit
|
||||
fi
|
||||
@@ -49,10 +55,9 @@ _dots_init_completion
|
||||
|
||||
_dots_load_plugins() {
|
||||
local plugin_dir="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/plugins"
|
||||
# autosuggestions first
|
||||
local f="$plugin_dir/zsh-autosuggestions/zsh-autosuggestions.zsh"
|
||||
[[ -f "$f" ]] && source "$f"
|
||||
# syntax-highlighting must be last
|
||||
# syntax-highlighting must be sourced last
|
||||
f="$plugin_dir/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
|
||||
[[ -f "$f" ]] && source "$f"
|
||||
}
|
||||
@@ -62,7 +67,7 @@ _dots_load_history() {
|
||||
HISTFILE="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/history"
|
||||
HISTSIZE=50000
|
||||
SAVEHIST=50000
|
||||
mkdir -p "$(dirname "$HISTFILE")"
|
||||
[[ -d "${HISTFILE:h}" ]] || mkdir -p "${HISTFILE:h}"
|
||||
setopt HIST_IGNORE_DUPS HIST_IGNORE_SPACE SHARE_HISTORY
|
||||
}
|
||||
_dots_load_history
|
||||
@@ -70,10 +75,7 @@ _dots_load_history
|
||||
_dots_load_keybindings() {
|
||||
bindkey -e
|
||||
|
||||
bindkey '^[[H' beginning-of-line
|
||||
bindkey '^[[F' end-of-line
|
||||
|
||||
# Ctrl+J: interactive zoxide jump (zi)
|
||||
# Ctrl+J: zoxide jump
|
||||
_dots_zoxide_widget() {
|
||||
local result
|
||||
result="$(zoxide query -i -- 2>&1)" && cd "$result"
|
||||
@@ -81,6 +83,104 @@ _dots_load_keybindings() {
|
||||
}
|
||||
zle -N _dots_zoxide_widget
|
||||
bindkey '^J' _dots_zoxide_widget
|
||||
|
||||
# Ctrl+B: git branch checkout
|
||||
_dots_git_branch_widget() {
|
||||
local branch
|
||||
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
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_git_branch_widget
|
||||
bindkey '^B' _dots_git_branch_widget
|
||||
|
||||
# Ctrl+E: edit file
|
||||
_dots_edit_widget() {
|
||||
local file
|
||||
file="$(fzf --preview 'head -100 {}')" || { zle reset-prompt; return; }
|
||||
${EDITOR:-vim} "$file" </dev/tty
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_edit_widget
|
||||
bindkey '^E' _dots_edit_widget
|
||||
|
||||
# Ctrl+G: SSH host picker
|
||||
_dots_ssh_hosts() {
|
||||
local ssh_log="${XDG_DATA_HOME:-$HOME/.local/share}/ssh/log"
|
||||
{
|
||||
if [[ -f "$ssh_log" ]]; then
|
||||
awk '{c[$2]++; t[$2]=$1} END {for(h in c) print c[h]*1000+t[h], h}' "$ssh_log" | sort -rn | awk '{print $2}'
|
||||
fi
|
||||
awk '/^Host / && !/\*/ {print $2}' ~/.ssh/config ~/.ssh/config.d/* 2>/dev/null
|
||||
awk '{print $1}' ~/.ssh/known_hosts 2>/dev/null | tr ',' '\n' | sed 's/\[//;s/\]:.*//'
|
||||
} | awk '!seen[$0]++'
|
||||
}
|
||||
_dots_ssh_widget() {
|
||||
local host
|
||||
host="$(_dots_ssh_hosts | fzf)" || { zle reset-prompt; return; }
|
||||
BUFFER="ssh $host"
|
||||
zle accept-line
|
||||
}
|
||||
zle -N _dots_ssh_widget
|
||||
bindkey '^G' _dots_ssh_widget
|
||||
|
||||
# Ctrl+F: find in files
|
||||
_dots_find_in_files_widget() {
|
||||
local selection
|
||||
selection="$(rg --color=always --line-number --no-heading '' 2>/dev/null \
|
||||
| fzf --ansi --delimiter=: \
|
||||
--preview 'head -n $((({2}+30))) {1} | tail -n 60' \
|
||||
--preview-window='right:60%')" || { zle reset-prompt; return; }
|
||||
local file="${selection%%:*}"
|
||||
local line="${${selection#*:}%%:*}"
|
||||
${EDITOR:-vim} "+$line" "$file" </dev/tty
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_find_in_files_widget
|
||||
bindkey '^F' _dots_find_in_files_widget
|
||||
|
||||
# Ctrl+N: tmux session
|
||||
_dots_tmux_widget() {
|
||||
if [[ -z "$TMUX" ]]; then
|
||||
tmux new-session </dev/tty
|
||||
else
|
||||
local session
|
||||
session="$(tmux list-sessions -F '#{session_name}' 2>/dev/null \
|
||||
| fzf --preview 'tmux list-windows -t {} -F " #{window_index}: #{window_name} #{pane_current_command}"')" \
|
||||
|| { zle reset-prompt; return; }
|
||||
tmux switch-client -t "$session"
|
||||
fi
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_tmux_widget
|
||||
bindkey '^N' _dots_tmux_widget
|
||||
|
||||
# Ctrl+O: open in browser
|
||||
_dots_open_widget() {
|
||||
local choice
|
||||
choice="$(printf 'repo\npr\nissues\nactions' | fzf)" || { zle reset-prompt; return; }
|
||||
case "$choice" in
|
||||
repo) gh browse 2>/dev/null ;;
|
||||
pr) gh pr view --web 2>/dev/null ;;
|
||||
issues) gh browse --issues 2>/dev/null ;;
|
||||
actions) gh browse --actions 2>/dev/null ;;
|
||||
esac
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_open_widget
|
||||
bindkey '^O' _dots_open_widget
|
||||
|
||||
# Ctrl+P: project switch
|
||||
_dots_project_widget() {
|
||||
local result
|
||||
result="$(zoxide query -l 2>/dev/null | grep "${WORKSPACE:-$HOME/Workspace}" \
|
||||
| fzf --preview 'ls -1 {}')" && cd "$result"
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N _dots_project_widget
|
||||
bindkey '^P' _dots_project_widget
|
||||
}
|
||||
_dots_load_keybindings
|
||||
|
||||
@@ -89,11 +189,10 @@ _dots_load_fzf() {
|
||||
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"'
|
||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||
export FZF_DEFAULT_OPTS='--layout=reverse --height=40% --prompt="> " --info=inline-right --no-separator'
|
||||
# Modern fzf (v0.48+) provides --zsh
|
||||
# fzf --zsh requires v0.48+
|
||||
if fzf --zsh &>/dev/null; then
|
||||
source <(fzf --zsh)
|
||||
else
|
||||
# Fallback paths for shell integration
|
||||
local -a fzf_paths=(
|
||||
"${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell"
|
||||
"/usr/share/fzf"
|
||||
|
||||
26
script/install.d/24-tmux.sh
Normal file
26
script/install.d/24-tmux.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Description:
|
||||
# Install and configure tmux.
|
||||
#
|
||||
|
||||
if ! command -v tmux &> /dev/null; then
|
||||
case "$DOTS_PKG" in
|
||||
apt)
|
||||
sudo apt-get install -qq tmux
|
||||
;;
|
||||
pacman)
|
||||
sudo pacman -S --noconfirm tmux
|
||||
;;
|
||||
brew)
|
||||
brew install tmux
|
||||
;;
|
||||
*)
|
||||
log_warn "Skipping tmux install: no supported package manager found"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
tmux -V
|
||||
@@ -69,6 +69,7 @@ binaries: List[Text] = [
|
||||
"fzf",
|
||||
"zoxide",
|
||||
"rg",
|
||||
"tmux",
|
||||
]
|
||||
binaries = [binary for binary in binaries if binary is not None]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user