# ============================================================================ # Tmux Workspace Manager - Project Templates & Layouts # ============================================================================ # Quick project workspace setup with pre-configured tmux layouts # # Usage: # tw-create [template] # Create workspace from template # tw-attach # Attach to workspace # tw-list # List all workspaces # tw-delete # Delete workspace # tw-save # Save current layout as template # tw # Quick attach (or create if not exists) # # Templates: # dev - Vim (50%) + terminal (25%) + logs (25%) # ops - 4 panes: htop, logs, shell, monitoring # ssh-multi - 4 panes for managing multiple servers # debug - 2 panes: main (70%) + helper (30%) # full - Just one full pane # # Add to .zshrc: # source ~/.dotfiles/zsh/functions/tmux-workspaces.zsh # ============================================================================ # ============================================================================ # Configuration # ============================================================================ typeset -g TW_TEMPLATES_DIR="${TW_TEMPLATES_DIR:-$HOME/.dotfiles/.tmux-templates}" typeset -g TW_SESSION_PREFIX="${TW_SESSION_PREFIX:-work}" typeset -g TW_DEFAULT_TEMPLATE="${TW_DEFAULT_TEMPLATE:-dev}" # Colors typeset -g TW_GREEN=$'\033[0;32m' typeset -g TW_BLUE=$'\033[0;34m' typeset -g TW_YELLOW=$'\033[1;33m' typeset -g TW_CYAN=$'\033[0;36m' typeset -g TW_RED=$'\033[0;31m' typeset -g TW_NC=$'\033[0m' # ============================================================================ # Helper Functions # ============================================================================ _tw_print_step() { echo -e "${TW_BLUE}==>${TW_NC} $1" } _tw_print_success() { echo -e "${TW_GREEN}✓${TW_NC} $1" } _tw_print_error() { echo -e "${TW_RED}✗${TW_NC} $1" } _tw_print_info() { echo -e "${TW_CYAN}ℹ${TW_NC} $1" } _tw_check_tmux() { if ! command -v tmux &>/dev/null; then _tw_print_error "tmux not installed" return 1 fi return 0 } _tw_init_templates() { mkdir -p "$TW_TEMPLATES_DIR" # Create default templates if they don't exist if [[ ! -f "$TW_TEMPLATES_DIR/dev.tmux" ]]; then _tw_create_default_templates fi } # ============================================================================ # Default Template Definitions # ============================================================================ _tw_create_default_templates() { _tw_print_step "Creating default templates..." # Development template - vim + terminal + logs cat > "$TW_TEMPLATES_DIR/dev.tmux" << 'EOF' # Development workspace # Usage: tw-create myproject dev # Split vertically (vim on left 50%, rest on right) split-window -h -p 50 # Split right pane horizontally (terminal top, logs bottom) split-window -v -p 50 # Select the first pane (vim) select-pane -t 0 # Optional: Start vim in first pane # send-keys -t 0 'vim' C-m # Optional: Set pane titles # select-pane -t 0 -T "Editor" # select-pane -t 1 -T "Terminal" # select-pane -t 2 -T "Logs" EOF # Operations template - 4 panes for monitoring cat > "$TW_TEMPLATES_DIR/ops.tmux" << 'EOF' # Operations workspace # 4-pane layout for system monitoring # Create 2x2 grid split-window -h -p 50 split-window -v -p 50 select-pane -t 0 split-window -v -p 50 # Optional: Auto-start monitoring tools # send-keys -t 0 'htop' C-m # send-keys -t 1 'docker ps' C-m # send-keys -t 2 '' C-m # send-keys -t 3 'tail -f /var/log/syslog' C-m select-pane -t 0 EOF # SSH multi-server template cat > "$TW_TEMPLATES_DIR/ssh-multi.tmux" << 'EOF' # Multi-server SSH workspace # 4 panes for managing multiple servers # Create 2x2 grid split-window -h -p 50 split-window -v -p 50 select-pane -t 0 split-window -v -p 50 # Enable pane synchronization (optional - uncomment to enable) # set-window-option synchronize-panes on select-pane -t 0 EOF # Debug template - main + helper pane cat > "$TW_TEMPLATES_DIR/debug.tmux" << 'EOF' # Debug workspace # Main pane (70%) + helper pane (30%) split-window -h -p 30 select-pane -t 0 EOF # Full template - single pane cat > "$TW_TEMPLATES_DIR/full.tmux" << 'EOF' # Full workspace # Single full-screen pane (default tmux behavior) EOF # Code review template - side-by-side comparison cat > "$TW_TEMPLATES_DIR/review.tmux" << 'EOF' # Code Review workspace # Two equal panes side-by-side for comparison split-window -h -p 50 select-pane -t 0 EOF _tw_print_success "Created default templates in: $TW_TEMPLATES_DIR" } # ============================================================================ # Template Management # ============================================================================ tw-templates() { _tw_init_templates echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" echo -e "${TW_BLUE}║${TW_NC} Available Tmux Templates ${TW_BLUE}║${TW_NC}" echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" echo for template in "$TW_TEMPLATES_DIR"/*.tmux; do [[ ! -f "$template" ]] && continue local name=$(basename "$template" .tmux) local description=$(grep "^#" "$template" | head -2 | tail -1 | sed 's/^# *//') echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$name${TW_NC}" [[ -n "$description" ]] && echo " $description" done echo echo "Create workspace: ${TW_CYAN}tw-create myproject dev${TW_NC}" echo "Quick attach: ${TW_CYAN}tw myproject${TW_NC}" } tw-template-edit() { local template_name="$1" if [[ -z "$template_name" ]]; then echo "Usage: tw-template-edit " echo tw-templates return 1 fi _tw_init_templates local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" ${EDITOR:-vim} "$template_file" _tw_print_success "Template edited: $template_name" } # ============================================================================ # Workspace Management # ============================================================================ tw-create() { local workspace_name="$1" local template="${2:-$TW_DEFAULT_TEMPLATE}" if [[ -z "$workspace_name" ]]; then echo "Usage: tw-create [template]" echo tw-templates return 1 fi _tw_check_tmux || return 1 _tw_init_templates local session_name="${TW_SESSION_PREFIX}-${workspace_name}" # Check if session already exists if tmux has-session -t "$session_name" 2>/dev/null; then _tw_print_error "Workspace '$workspace_name' already exists" echo "Use: ${TW_CYAN}tw $workspace_name${TW_NC} to attach" return 1 fi # Check if template exists local template_file="$TW_TEMPLATES_DIR/${template}.tmux" if [[ ! -f "$template_file" ]]; then _tw_print_error "Template '$template' not found" tw-templates return 1 fi _tw_print_step "Creating workspace: $workspace_name (template: $template)" # Create new tmux session (detached) tmux new-session -d -s "$session_name" # Apply template _tw_print_step "Applying template: $template" tmux source-file "$template_file" -t "$session_name" # Set working directory if we're in a git repo or specific directory if git rev-parse --git-dir &>/dev/null 2>&1; then local git_root=$(git rev-parse --show-toplevel) _tw_print_info "Setting workspace directory to: $git_root" tmux send-keys -t "$session_name:0" "cd $git_root" C-m fi _tw_print_success "Workspace created: $workspace_name" # Attach if not already in tmux if [[ -z "$TMUX" ]]; then _tw_print_step "Attaching to workspace..." tmux attach-session -t "$session_name" else _tw_print_info "Switch with: ${TW_CYAN}tmux switch-client -t $session_name${TW_NC}" fi } tw-attach() { local workspace_name="$1" if [[ -z "$workspace_name" ]]; then echo "Usage: tw-attach " echo tw-list return 1 fi _tw_check_tmux || return 1 local session_name="${TW_SESSION_PREFIX}-${workspace_name}" if ! tmux has-session -t "$session_name" 2>/dev/null; then _tw_print_error "Workspace '$workspace_name' not found" echo echo "Create it with: ${TW_CYAN}tw-create $workspace_name${TW_NC}" return 1 fi # Attach or switch if [[ -z "$TMUX" ]]; then tmux attach-session -t "$session_name" else tmux switch-client -t "$session_name" fi } tw-list() { _tw_check_tmux || return 1 echo -e "${TW_BLUE}╔════════════════════════════════════════════════════════════╗${TW_NC}" echo -e "${TW_BLUE}║${TW_NC} Active Tmux Workspaces ${TW_BLUE}║${TW_NC}" echo -e "${TW_BLUE}╚════════════════════════════════════════════════════════════╝${TW_NC}" echo local has_workspaces=false # List all tmux sessions tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do # Only show sessions with our prefix if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then has_workspaces=true local workspace_name="${session_full#${TW_SESSION_PREFIX}-}" local attached="" # Check if currently attached if [[ -n "$TMUX" ]]; then local current_session=$(tmux display-message -p '#S') [[ "$current_session" == "$session_full" ]] && attached=" ${TW_GREEN}(current)${TW_NC}" fi echo -e "${TW_GREEN}●${TW_NC} ${TW_CYAN}$workspace_name${TW_NC}$attached" echo " Session: $session_full" fi done if [[ "$has_workspaces" != true ]]; then _tw_print_info "No active workspaces" echo echo "Create one with: ${TW_CYAN}tw-create myproject${TW_NC}" fi } tw-delete() { local workspace_name="$1" if [[ -z "$workspace_name" ]]; then echo "Usage: tw-delete " echo tw-list return 1 fi _tw_check_tmux || return 1 local session_name="${TW_SESSION_PREFIX}-${workspace_name}" if ! tmux has-session -t "$session_name" 2>/dev/null; then _tw_print_error "Workspace '$workspace_name' not found" return 1 fi # Kill session tmux kill-session -t "$session_name" _tw_print_success "Deleted workspace: $workspace_name" } # ============================================================================ # Save Current Layout as Template # ============================================================================ tw-save() { local template_name="$1" if [[ -z "$template_name" ]]; then echo "Usage: tw-save " echo echo "Saves the current tmux window layout as a reusable template" return 1 fi _tw_check_tmux || return 1 if [[ -z "$TMUX" ]]; then _tw_print_error "Must be run from inside tmux" return 1 fi _tw_init_templates local template_file="$TW_TEMPLATES_DIR/${template_name}.tmux" if [[ -f "$template_file" ]]; then read -q "REPLY?Template '$template_name' exists. Overwrite? [y/N]: " echo [[ ! "$REPLY" =~ ^[Yy]$ ]] && return 1 fi _tw_print_step "Saving current layout as template: $template_name" # Get current window layout local layout=$(tmux display-message -p '#{window_layout}') local pane_count=$(tmux display-message -p '#{window_panes}') # Create template with layout commands cat > "$template_file" << EOF # Custom template: $template_name # Saved: $(date) # Panes: $pane_count # Note: This is a simplified layout recreation # You may need to adjust split percentages and commands EOF # Generate split commands based on pane count if (( pane_count == 2 )); then echo "split-window -h -p 50" >> "$template_file" elif (( pane_count == 3 )); then cat >> "$template_file" << 'EOF' split-window -h -p 50 split-window -v -p 50 EOF elif (( pane_count == 4 )); then cat >> "$template_file" << 'EOF' split-window -h -p 50 split-window -v -p 50 select-pane -t 0 split-window -v -p 50 EOF fi echo "" >> "$template_file" echo "select-pane -t 0" >> "$template_file" _tw_print_success "Template saved: $template_name" echo " File: $template_file" echo " Edit: ${TW_CYAN}tw-template-edit $template_name${TW_NC}" } # ============================================================================ # Quick Workspace (attach or create) # ============================================================================ tw() { local workspace_name="$1" local template="${2:-$TW_DEFAULT_TEMPLATE}" if [[ -z "$workspace_name" ]]; then tw-list return 0 fi _tw_check_tmux || return 1 local session_name="${TW_SESSION_PREFIX}-${workspace_name}" # If session exists, attach. Otherwise create. if tmux has-session -t "$session_name" 2>/dev/null; then tw-attach "$workspace_name" else _tw_print_info "Workspace doesn't exist. Creating with template: $template" tw-create "$workspace_name" "$template" fi } # ============================================================================ # Fuzzy Search (requires fzf) # ============================================================================ twf() { if ! command -v fzf &>/dev/null; then _tw_print_error "fzf not installed" return 1 fi _tw_check_tmux || return 1 # Get list of sessions local sessions=() tmux list-sessions 2>/dev/null | while IFS=: read -r session_full rest; do if [[ "$session_full" == ${TW_SESSION_PREFIX}-* ]]; then local workspace_name="${session_full#${TW_SESSION_PREFIX}-}" sessions+=("$workspace_name") fi done if [[ ${#sessions[@]} -eq 0 ]]; then _tw_print_info "No workspaces found" return 1 fi # Fuzzy select local selection=$(printf '%s\n' "${sessions[@]}" | \ fzf --height=40% \ --layout=reverse \ --border=rounded \ --prompt='Workspace > ' \ --preview='tmux list-windows -t work-{} 2>/dev/null || echo "No preview"') if [[ -n "$selection" ]]; then tw-attach "$selection" fi } # ============================================================================ # Pane Synchronization Toggle # ============================================================================ tw-sync() { if [[ -z "$TMUX" ]]; then _tw_print_error "Must be run from inside tmux" return 1 fi local current=$(tmux show-window-option -v synchronize-panes 2>/dev/null) if [[ "$current" == "on" ]]; then tmux set-window-option synchronize-panes off _tw_print_info "Pane synchronization: ${TW_RED}OFF${TW_NC}" else tmux set-window-option synchronize-panes on _tw_print_info "Pane synchronization: ${TW_GREEN}ON${TW_NC}" fi } # ============================================================================ # Rename Workspace # ============================================================================ tw-rename() { local old_name="$1" local new_name="$2" if [[ -z "$old_name" || -z "$new_name" ]]; then echo "Usage: tw-rename " return 1 fi _tw_check_tmux || return 1 local old_session="${TW_SESSION_PREFIX}-${old_name}" local new_session="${TW_SESSION_PREFIX}-${new_name}" if ! tmux has-session -t "$old_session" 2>/dev/null; then _tw_print_error "Workspace '$old_name' not found" return 1 fi tmux rename-session -t "$old_session" "$new_session" _tw_print_success "Renamed: $old_name → $new_name" } # ============================================================================ # Aliases # ============================================================================ alias twl='tw-list' alias twc='tw-create' alias twa='tw-attach' alias twd='tw-delete' alias tws='tw-save' alias twt='tw-templates' alias twe='tw-template-edit' # ============================================================================ # Initialization # ============================================================================ _tw_init_templates