diff options
Diffstat (limited to 'scripts/tmux-sessionizer')
| -rwxr-xr-x | scripts/tmux-sessionizer | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/scripts/tmux-sessionizer b/scripts/tmux-sessionizer new file mode 100755 index 0000000..e7251da --- /dev/null +++ b/scripts/tmux-sessionizer @@ -0,0 +1,344 @@ +#!/usr/bin/env bash +CONFIG_FILE_NAME="tmux-sessionizer.conf" +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/tmux-sessionizer" +CONFIG_FILE="$CONFIG_DIR/$CONFIG_FILE_NAME" +PANE_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/tmux-sessionizer" +PANE_CACHE_FILE="$PANE_CACHE_DIR/panes.cache" + +# config file example +# ------------------------ +# # file: ~/.config/tmux-sessionizer/tmux-sessionizer.conf +# # If set this override the default TS_SEARCH_PATHS (~/ ~/personal ~/personal/dev/env/.config) +TS_SEARCH_PATHS=(~/git ~/Documents ~/.config ~/.local) +# # If set this add additional search paths to the default TS_SEARCH_PATHS +# # The number prefix is the depth for the Path [OPTIONAL] +# TS_EXTRA_SEARCH_PATHS=(~/ghq:3 ~/Git:3 ~/.config:2) +# # if set this override the TS_MAX_DEPTH (1) +# TS_MAX_DEPTH=2 +# This is not meant to override .tmux-sessionizer. At first i thought this +# would be a good command, but i don't think its ackshually what i want. +# +# Instead, its a list of commands to run on windows who's index is way outside +# of the first 10 windows. This allows you to create as many windows in your +# session as you would like without having your workflow interrupted by these +# programatic windows +# +# how to use: +# tmux-sessionizer -w 0 will execute the first command in window -t 69. +# TS_SESSION_COMMANDS=(<cmd1> <cmd2>) +# +# TS_LOG=true # will write logs to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs +# TS_LOG_FILE=<file> # will write logs to <file> Defaults to ~/.local/share/tmux-sessionizer/tmux-sessionizer.logs +# ------------------------ + +if [[ -f "$CONFIG_FILE" ]]; then + source "$CONFIG_FILE" +fi + +if [[ -f "$CONFIG_FILE_NAME" ]]; then + source "$CONFIG_FILE_NAME" +fi + +if [[ $TS_LOG != "true" ]]; then + if [[ -z $TS_LOG_FILE ]]; then + TS_LOG_FILE="$HOME/.local/share/tmux-sessionizer/tmux-sessionizer.logs" + fi + + mkdir -p "$(dirname "$TS_LOG_FILE")" +fi + +log() { + if [[ -z $TS_LOG ]]; then + return + elif [[ $TS_LOG == "echo" ]]; then + echo "$*" + elif [[ $TS_LOG == "file" ]]; then + echo "$*" >>"$TS_LOG_FILE" + fi +} + +session_idx="" +session_cmd="" +user_selected="" +split_type="" +VERSION="0.1.0" + +while [[ "$#" -gt 0 ]]; do + case "$1" in + -h | --help) + echo "Usage: tmux-sessionizer [OPTIONS] [SEARCH_PATH]" + echo "Options:" + echo " -h, --help Display this help message" + echo " -s, --session <name> session command index." + echo " --vsplit Create vertical split (horizontal layout) for session command" + echo " --hsplit Create horizontal split (vertical layout) for session command" + exit 0 + ;; + -s | --session) + session_idx="$2" + if [[ -z $session_idx ]]; then + echo "Session index cannot be empty" + exit 1 + fi + + if [[ -z $TS_SESSION_COMMANDS ]]; then + echo "TS_SESSION_COMMANDS is not set. Must have a command set to run when switching to a session" + exit 1 + fi + + if [[ -z "$session_idx" || "$session_idx" -lt 0 || "$session_idx" -ge "${#TS_SESSION_COMMANDS[@]}" ]]; then + echo "Error: Invalid index. Please provide an index between 0 and $((${#TS_SESSION_COMMANDS[@]} - 1))." + exit 1 + fi + + session_cmd="${TS_SESSION_COMMANDS[$session_idx]}" + + shift + ;; + --vsplit) + split_type="vsplit" + ;; + --hsplit) + split_type="hsplit" + ;; + -v | --version) + echo "tmux-sessionizer version $VERSION" + exit 0 + ;; + *) + user_selected="$1" + ;; + esac + shift +done + +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" + +# Validate split options are only used with session commands +if [[ -n "$split_type" && -z "$session_idx" ]]; then + echo "Error: --vsplit and --hsplit can only be used with -s/--session option" + exit 1 +fi + +sanity_check() { + if ! command -v tmux &>/dev/null; then + echo "tmux is not installed. Please install it first." + exit 1 + fi + + if ! command -v fzf &>/dev/null; then + echo "fzf is not installed. Please install it first." + exit 1 + fi +} + +switch_to() { + if [[ -z $TMUX ]]; then + log "attaching to session $1" + tmux attach-session -t "$1" + else + log "switching to session $1" + tmux switch-client -t "$1" + fi +} + +has_session() { + tmux list-sessions | grep -q "^$1:" +} + +hydrate() { + if [[ ! -z $session_cmd ]]; then + log "skipping hydrate for $1 -- using \"$session_cmd\" instead" + return + elif [ -f "$2/.tmux-sessionizer" ]; then + log "sourcing(local) $2/.tmux-sessionizer" + tmux send-keys -t "$1" "source $2/.tmux-sessionizer" c-M + elif [ -f "$HOME/.tmux-sessionizer" ]; then + log "sourcing(global) $HOME/.tmux-sessionizer" + tmux send-keys -t "$1" "source $HOME/.tmux-sessionizer" c-M + fi +} + +is_tmux_running() { + tmux_running=$(pgrep tmux) + + if [[ -z $TMUX ]] && [[ -z $tmux_running ]]; then + return 1 + fi + return 0 +} + +init_pane_cache() { + mkdir -p "$PANE_CACHE_DIR" + touch "$PANE_CACHE_FILE" +} + +get_pane_id() { + local session_idx="$1" + local split_type="$2" + init_pane_cache + grep "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" | cut -d: -f3 +} + +set_pane_id() { + local session_idx="$1" + local split_type="$2" + local pane_id="$3" + init_pane_cache + + # Remove existing entry if it exists + grep -v "^${session_idx}:${split_type}:" "$PANE_CACHE_FILE" >"${PANE_CACHE_FILE}.tmp" 2>/dev/null || true + mv "${PANE_CACHE_FILE}.tmp" "$PANE_CACHE_FILE" + + # Add new entry + echo "${session_idx}:${split_type}:${pane_id}" >>"$PANE_CACHE_FILE" +} + +cleanup_dead_panes() { + init_pane_cache + local temp_file="${PANE_CACHE_FILE}.tmp" + + while IFS=: read -r idx split pane_id; do + if tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${pane_id}$"; then + echo "${idx}:${split}:${pane_id}" >>"$temp_file" + fi + done <"$PANE_CACHE_FILE" + + mv "$temp_file" "$PANE_CACHE_FILE" 2>/dev/null || touch "$PANE_CACHE_FILE" +} + +sanity_check + +# if TS_SEARCH_PATHS is not set use default +[[ -n "$TS_SEARCH_PATHS" ]] || TS_SEARCH_PATHS=(~/ ~/personal ~/personal/dev/env/.config) + +# Add any extra search paths to the TS_SEARCH_PATHS array +# e.g : EXTRA_SEARCH_PATHS=("$HOME/extra1:4" "$HOME/extra2") +# note : Path can be suffixed with :number to limit or extend the depth of the search for the Path + +if [[ ${#TS_EXTRA_SEARCH_PATHS[@]} -gt 0 ]]; then + TS_SEARCH_PATHS+=("${TS_EXTRA_SEARCH_PATHS[@]}") +fi + +# utility function to find directories +find_dirs() { + # list TMUX sessions + if [[ -n "${TMUX}" ]]; then + current_session=$(tmux display-message -p '#S') + tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null | grep -vFx "[TMUX] $current_session" + else + tmux list-sessions -F "[TMUX] #{session_name}" 2>/dev/null + fi + + # note: TS_SEARCH_PATHS is an array of paths to search for directories + # if the path ends with :number, it will search for directories with a max depth of number ;) + # if there is no number, it will search for directories with a max depth defined by TS_MAX_DEPTH or 1 if not set + for entry in "${TS_SEARCH_PATHS[@]}"; do + # Check if entry as :number as suffix then adapt the maxdepth parameter + if [[ "$entry" =~ ^([^:]+):([0-9]+)$ ]]; then + path="${BASH_REMATCH[1]}" + depth="${BASH_REMATCH[2]}" + else + path="$entry" + fi + + [[ -d "$path" ]] && find "$path" -mindepth 1 -maxdepth "${depth:-${TS_MAX_DEPTH:-1}}" -path '*/.git' -prune -o -type d -print + done +} + +handle_session_cmd() { + log "executing session command $session_cmd with index $session_idx split_type=$split_type" + if ! is_tmux_running; then + echo "Error: tmux is not running. Please start tmux first before using session commands." + exit 1 + fi + + current_session=$(tmux display-message -p '#S') + + if [[ -n "$split_type" ]]; then + handle_split_session_cmd "$current_session" + else + handle_window_session_cmd "$current_session" + fi + exit 0 +} + +handle_window_session_cmd() { + local current_session="$1" + start_index=$((69 + $session_idx)) + target="$current_session:$start_index" + + log "target: $target command $session_cmd has-session=$(tmux has-session -t="$target" 2>/dev/null)" + if tmux has-session -t="$target" 2>/dev/null; then + switch_to "$target" + else + log "executing session command: tmux neww -dt $target $session_cmd" + tmux neww -dt $target "$session_cmd" + hydrate "$target" "$selected" + tmux select-window -t $target + fi +} + +handle_split_session_cmd() { + local current_session="$1" + cleanup_dead_panes + + # Check if pane already exists + local existing_pane_id=$(get_pane_id "$session_idx" "$split_type") + + if [[ -n "$existing_pane_id" ]] && tmux list-panes -a -F "#{pane_id}" 2>/dev/null | grep -q "^${existing_pane_id}$"; then + log "switching to existing pane $existing_pane_id" + tmux select-pane -t "$existing_pane_id" + if [[ -z $TMUX ]]; then + tmux attach-session -t "$current_session" + else + tmux switch-client -t "$current_session" + fi + else + # Create new split + local split_flag="" + if [[ "$split_type" == "vsplit" ]]; then + split_flag="-h" # horizontal layout (vertical split) + else + split_flag="-v" # vertical layout (horizontal split) + fi + + log "creating new split: tmux split-window $split_flag -c $(pwd) $session_cmd" + local new_pane_id=$(tmux split-window $split_flag -c "$(pwd)" -P -F "#{pane_id}" "$session_cmd") + + if [[ -n "$new_pane_id" ]]; then + set_pane_id "$session_idx" "$split_type" "$new_pane_id" + log "created pane $new_pane_id for session_idx=$session_idx split_type=$split_type" + fi + fi +} + +if [[ ! -z $session_cmd ]]; then + handle_session_cmd +elif [[ ! -z $user_selected ]]; then + selected="$user_selected" +else + selected=$(find_dirs | fzf) +fi + +if [[ -z $selected ]]; then + exit 0 +fi + +if [[ "$selected" =~ ^\[TMUX\]\ (.+)$ ]]; then + selected="${BASH_REMATCH[1]}" +fi + +selected_name=$(basename "$selected" | tr . _) + +if ! is_tmux_running; then + tmux new-session -ds "$selected_name" -c "$selected" + hydrate "$selected_name" "$selected" +fi + +if ! has_session "$selected_name"; then + tmux new-session -ds "$selected_name" -c "$selected" + hydrate "$selected_name" "$selected" +fi + +switch_to "$selected_name" |
