summaryrefslogtreecommitdiff
path: root/.local/bin/tmux-sessionizer
diff options
context:
space:
mode:
Diffstat (limited to '.local/bin/tmux-sessionizer')
-rwxr-xr-x.local/bin/tmux-sessionizer345
1 files changed, 345 insertions, 0 deletions
diff --git a/.local/bin/tmux-sessionizer b/.local/bin/tmux-sessionizer
new file mode 100755
index 0000000..40705d0
--- /dev/null
+++ b/.local/bin/tmux-sessionizer
@@ -0,0 +1,345 @@
+#!/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)
+TS_EXTRA_SEARCH_PATHS=(~/projects ~/documents ~/.config ~/.local)
+# # 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"