nirish

config files for niri window manager
Log | Files | Refs | README | LICENSE

tmux-sessionizer (10234B)


      1 #!/usr/bin/env bash
      2 CONFIG_FILE_NAME="tmux-sessionizer.conf"
      3 CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/tmux-sessionizer"
      4 CONFIG_FILE="$CONFIG_DIR/$CONFIG_FILE_NAME"
      5 PANE_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/tmux-sessionizer"
      6 PANE_CACHE_FILE="$PANE_CACHE_DIR/panes.cache"
      7 
      8 # config file example
      9 # ------------------------
     10 # # file: ~/.config/tmux-sessionizer/tmux-sessionizer.conf
     11 # # If set this override the default TS_SEARCH_PATHS (~/ ~/personal ~/personal/dev/env/.config)
     12 # TS_SEARCH_PATHS=(~/git ~/Documents ~/.config ~/.local)
     13 # # If set this add additional search paths to the default TS_SEARCH_PATHS
     14 # # The number prefix is the depth for the Path [OPTIONAL]
     15 # TS_EXTRA_SEARCH_PATHS=(~/ghq:3 ~/Git:3 ~/.config:2)
     16 TS_EXTRA_SEARCH_PATHS=(~/git ~/Documents ~/.config ~/.local)
     17 # # if set this override the TS_MAX_DEPTH (1)
     18 # TS_MAX_DEPTH=2
     19 # This is not meant to override .tmux-sessionizer.  At first i thought this
     20 # would be a good command, but i don't think its ackshually what i want.
     21 #
     22 # Instead, its a list of commands to run on windows who's index is way outside
     23 # of the first 10 windows.  This allows you to create as many windows in your
     24 # session as you would like without having your workflow interrupted by these
     25 # programatic windows
     26 #
     27 # how to use:
     28 # tmux-sessionizer -w 0 will execute the first command in window -t 69.
     29 # TS_SESSION_COMMANDS=(<cmd1> <cmd2>)
     30 #
     31 # TS_LOG=true # will write logs to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs
     32 # TS_LOG_FILE=<file> # will write logs to <file> Defaults to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs
     33 # ------------------------
     34 
     35 if [[ -f "$CONFIG_FILE" ]]; then
     36   source "$CONFIG_FILE"
     37 fi
     38 
     39 if [[ -f "$CONFIG_FILE_NAME" ]]; then
     40   source "$CONFIG_FILE_NAME"
     41 fi
     42 
     43 if [[ $TS_LOG != "true" ]]; then
     44   if [[ -z $TS_LOG_FILE ]]; then
     45     TS_LOG_FILE="$HOME/.local/share/tmux-sessionizer/tmux-sessionizer.logs"
     46   fi
     47 
     48   mkdir -p "$(dirname "$TS_LOG_FILE")"
     49 fi
     50 
     51 log() {
     52   if [[ -z $TS_LOG ]]; then
     53     return
     54   elif [[ $TS_LOG == "echo" ]]; then
     55     echo "$*"
     56   elif [[ $TS_LOG == "file" ]]; then
     57     echo "$*" >>"$TS_LOG_FILE"
     58   fi
     59 }
     60 
     61 session_idx=""
     62 session_cmd=""
     63 user_selected=""
     64 split_type=""
     65 VERSION="0.1.0"
     66 
     67 while [[ "$#" -gt 0 ]]; do
     68   case "$1" in
     69   -h | --help)
     70     echo "Usage: tmux-sessionizer [OPTIONS] [SEARCH_PATH]"
     71     echo "Options:"
     72     echo "  -h, --help             Display this help message"
     73     echo "  -s, --session <name>   session command index."
     74     echo "  --vsplit               Create vertical split (horizontal layout) for session command"
     75     echo "  --hsplit               Create horizontal split (vertical layout) for session command"
     76     exit 0
     77     ;;
     78   -s | --session)
     79     session_idx="$2"
     80     if [[ -z $session_idx ]]; then
     81       echo "Session index cannot be empty"
     82       exit 1
     83     fi
     84 
     85     if [[ -z $TS_SESSION_COMMANDS ]]; then
     86       echo "TS_SESSION_COMMANDS is not set.  Must have a command set to run when switching to a session"
     87       exit 1
     88     fi
     89 
     90     if [[ -z "$session_idx" || "$session_idx" -lt 0 || "$session_idx" -ge "${#TS_SESSION_COMMANDS[@]}" ]]; then
     91       echo "Error: Invalid index. Please provide an index between 0 and $((${#TS_SESSION_COMMANDS[@]} - 1))."
     92       exit 1
     93     fi
     94 
     95     session_cmd="${TS_SESSION_COMMANDS[$session_idx]}"
     96 
     97     shift
     98     ;;
     99   --vsplit)
    100     split_type="vsplit"
    101     ;;
    102   --hsplit)
    103     split_type="hsplit"
    104     ;;
    105   -v | --version)
    106     echo "tmux-sessionizer version $VERSION"
    107     exit 0
    108     ;;
    109   *)
    110     user_selected="$1"
    111     ;;
    112   esac
    113   shift
    114 done
    115 
    116 log "tmux-sessionizer($VERSION): idx=$session_idx cmd=$session_cmd user_selected=$user_selected split_type=$split_type log=$TS_LOG log_file=$TS_LOG_FILE"
    117 
    118 # Validate split options are only used with session commands
    119 if [[ -n "$split_type" && -z "$session_idx" ]]; then
    120   echo "Error: --vsplit and --hsplit can only be used with -s/--session option"
    121   exit 1
    122 fi
    123 
    124 sanity_check() {
    125   if ! command -v tmux &>/dev/null; then
    126     echo "tmux is not installed. Please install it first."
    127     exit 1
    128   fi
    129 
    130   if ! command -v fzf &>/dev/null; then
    131     echo "fzf is not installed. Please install it first."
    132     exit 1
    133   fi
    134 }
    135 
    136 switch_to() {
    137   if [[ -z $TMUX ]]; then
    138     log "attaching to session $1"
    139     tmux attach-session -t "$1"
    140   else
    141     log "switching to session $1"
    142     tmux switch-client -t "$1"
    143   fi
    144 }
    145 
    146 has_session() {
    147   tmux list-sessions | grep -q "^$1:"
    148 }
    149 
    150 hydrate() {
    151   if [[ ! -z $session_cmd ]]; then
    152     log "skipping hydrate for $1 -- using \"$session_cmd\" instead"
    153     return
    154   elif [ -f "$2/.tmux-sessionizer" ]; then
    155     log "sourcing(local) $2/.tmux-sessionizer"
    156     tmux send-keys -t "$1" "source $2/.tmux-sessionizer" c-M
    157   elif [ -f "$HOME/.tmux-sessionizer" ]; then
    158     log "sourcing(global) $HOME/.tmux-sessionizer"
    159     tmux send-keys -t "$1" "source $HOME/.tmux-sessionizer" c-M
    160   fi
    161 }
    162 
    163 is_tmux_running() {
    164   tmux_running=$(pgrep tmux)
    165 
    166   if [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then
    167     return 1
    168   fi
    169   return 0
    170 }
    171 
    172 init_pane_cache() {
    173   mkdir -p "$PANE_CACHE_DIR"
    174   touch "$PANE_CACHE_FILE"
    175 }
    176 
    177 get_pane_id() {
    178   local session_idx="$1"
    179   local split_type="$2"
    180   init_pane_cache
    181   grep "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" | cut -d: -f3
    182 }
    183 
    184 set_pane_id() {
    185   local session_idx="$1"
    186   local split_type="$2"
    187   local pane_id="$3"
    188   init_pane_cache
    189 
    190   # Remove existing entry if it exists
    191   grep -v "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" >"${PANE_CACHE_FILE}.tmp" 2>/dev/null || true
    192   mv "${PANE_CACHE_FILE}.tmp" "$PANE_CACHE_FILE"
    193 
    194   # Add new entry
    195   echo "${session_idx}:${split_type}:${pane_id}" >>"$PANE_CACHE_FILE"
    196 }
    197 
    198 cleanup_dead_panes() {
    199   init_pane_cache
    200   local temp_file="${PANE_CACHE_FILE}.tmp"
    201 
    202   while IFS=: read -r idx split pane_id; do
    203     if tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${pane_id}$"; then
    204       echo "${idx}:${split}:${pane_id}" >>"$temp_file"
    205     fi
    206   done <"$PANE_CACHE_FILE"
    207 
    208   mv "$temp_file" "$PANE_CACHE_FILE" 2>/dev/null || touch "$PANE_CACHE_FILE"
    209 }
    210 
    211 sanity_check
    212 
    213 # if TS_SEARCH_PATHS is not set use default
    214 [[ -n "$TS_SEARCH_PATHS" ]] || TS_SEARCH_PATHS=(~/ ~/personal ~/personal/dev/env/.config)
    215 
    216 # Add any extra search paths to the TS_SEARCH_PATHS array
    217 # e.g : EXTRA_SEARCH_PATHS=("$HOME/extra1:4" "$HOME/extra2")
    218 # note : Path can be suffixed with :number to limit or extend the depth of the search for the Path
    219 
    220 if [[ ${#TS_EXTRA_SEARCH_PATHS[@]} -gt 0 ]]; then
    221   TS_SEARCH_PATHS+=("${TS_EXTRA_SEARCH_PATHS[@]}")
    222 fi
    223 
    224 # utility function to find directories
    225 find_dirs() {
    226   # list TMUX sessions
    227   if [[ -n "${TMUX}" ]]; then
    228     current_session=$(tmux display-message -p '#S')
    229     tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null | grep -vFx "[TMUX] $current_session"
    230   else
    231     tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null
    232   fi
    233 
    234   # note: TS_SEARCH_PATHS is an array of paths to search for directories
    235   # if the path ends with :number, it will search for directories with a max depth of number ;)
    236   # if there is no number, it will search for directories with a max depth defined by TS_MAX_DEPTH or 1 if not set
    237   for entry in "${TS_SEARCH_PATHS[@]}"; do
    238     # Check if entry as :number as suffix then adapt the maxdepth parameter
    239     if [[ "$entry" =~ ^([^:]+):([0-9]+)$ ]]; then
    240       path="${BASH_REMATCH[1]}"
    241       depth="${BASH_REMATCH[2]}"
    242     else
    243       path="$entry"
    244     fi
    245 
    246     [[ -d "$path" ]] && find "$path" -mindepth 1 -maxdepth "${depth:-${TS_MAX_DEPTH:-1}}" -path '*/.git' -prune -o -type d -print
    247   done
    248 }
    249 
    250 handle_session_cmd() {
    251   log "executing session command $session_cmd with index $session_idx split_type=$split_type"
    252   if ! is_tmux_running; then
    253     echo "Error: tmux is not running.  Please start tmux first before using session commands."
    254     exit 1
    255   fi
    256 
    257   current_session=$(tmux display-message -p '#S')
    258 
    259   if [[ -n "$split_type" ]]; then
    260     handle_split_session_cmd "$current_session"
    261   else
    262     handle_window_session_cmd "$current_session"
    263   fi
    264   exit 0
    265 }
    266 
    267 handle_window_session_cmd() {
    268   local current_session="$1"
    269   start_index=$((69 + $session_idx))
    270   target="$current_session:$start_index"
    271 
    272   log "target: $target command $session_cmd has-session=$(tmux has-session -t="$target" 2>/dev/null)"
    273   if tmux has-session -t="$target" 2>/dev/null; then
    274     switch_to "$target"
    275   else
    276     log "executing session command: tmux neww -dt $target $session_cmd"
    277     tmux neww -dt $target "$session_cmd"
    278     hydrate "$target" "$selected"
    279     tmux select-window -t $target
    280   fi
    281 }
    282 
    283 handle_split_session_cmd() {
    284   local current_session="$1"
    285   cleanup_dead_panes
    286 
    287   # Check if pane already exists
    288   local existing_pane_id=$(get_pane_id "$session_idx" "$split_type")
    289 
    290   if [[ -n "$existing_pane_id" ]] && tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${existing_pane_id}$"; then
    291     log "switching to existing pane $existing_pane_id"
    292     tmux select-pane -t "$existing_pane_id"
    293     if [[ -z $TMUX ]]; then
    294       tmux attach-session -t "$current_session"
    295     else
    296       tmux switch-client -t "$current_session"
    297     fi
    298   else
    299     # Create new split
    300     local split_flag=""
    301     if [[ "$split_type" == "vsplit" ]]; then
    302       split_flag="-h" # horizontal layout (vertical split)
    303     else
    304       split_flag="-v" # vertical layout (horizontal split)
    305     fi
    306 
    307     log "creating new split: tmux split-window $split_flag -c $(pwd) $session_cmd"
    308     local new_pane_id=$(tmux split-window $split_flag -c "$(pwd)" -P -F "#{pane_id}" "$session_cmd")
    309 
    310     if [[ -n "$new_pane_id" ]]; then
    311       set_pane_id "$session_idx" "$split_type" "$new_pane_id"
    312       log "created pane $new_pane_id for session_idx=$session_idx split_type=$split_type"
    313     fi
    314   fi
    315 }
    316 
    317 if [[ ! -z $session_cmd ]]; then
    318   handle_session_cmd
    319 elif [[ ! -z $user_selected ]]; then
    320   selected="$user_selected"
    321 else
    322   selected=$(find_dirs | fzf)
    323 fi
    324 
    325 if [[ -z $selected ]]; then
    326   exit 0
    327 fi
    328 
    329 if [[ "$selected" =~ ^\[TMUX\]\ (.+)$ ]]; then
    330   selected="${BASH_REMATCH[1]}"
    331 fi
    332 
    333 selected_name=$(basename "$selected" | tr . _)
    334 
    335 if ! is_tmux_running; then
    336   tmux new-session -ds "$selected_name" -c "$selected"
    337   hydrate "$selected_name" "$selected"
    338 fi
    339 
    340 if ! has_session "$selected_name"; then
    341   tmux new-session -ds "$selected_name" -c "$selected"
    342   hydrate "$selected_name" "$selected"
    343 fi
    344 
    345 switch_to "$selected_name"