diff --git a/local-bin/.local/bin/dev-launcher b/local-bin/.local/bin/dev-launcher index f6defb3..ff4d54c 100755 --- a/local-bin/.local/bin/dev-launcher +++ b/local-bin/.local/bin/dev-launcher @@ -5,12 +5,21 @@ set -euo pipefail CACHE_MAX_AGE=300 MRU_SIZE=20 +# Debug logging (set DEBUG=1 to enable) +DEBUG="${DEBUG:-0}" + +debug() { + [ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2 || true +} + # Parse arguments - flags first, then DEV_DIR NO_CACHE=false CLEAR_CACHE=false EDITOR="${EDITOR:-nvim}" DEV_DIR="" +debug "Starting dev-launcher with args: $*" + for arg in "$@"; do case "$arg" in --help | -h) @@ -37,11 +46,16 @@ EOF done DEV_DIR="${DEV_DIR:-$HOME/Dev}" +debug "DEV_DIR: $DEV_DIR" +debug "NO_CACHE: $NO_CACHE" +debug "CLEAR_CACHE: $CLEAR_CACHE" # Setup cache files (unique per dev directory) DEV_DIR_HASH=$(echo -n "$DEV_DIR" | sha256sum | cut -d' ' -f1 | head -c 16) CACHE_FILE="$HOME/.cache/dev-launcher-cache-${DEV_DIR_HASH}" MRU_FILE="$HOME/.cache/dev-launcher-mru-${DEV_DIR_HASH}" +debug "CACHE_FILE: $CACHE_FILE" +debug "MRU_FILE: $MRU_FILE" # Handle --clear-cache if [ "$CLEAR_CACHE" = true ]; then @@ -52,17 +66,29 @@ fi # Find git repositories find_git_repos() { + debug "find_git_repos: checking cache..." if [ "$NO_CACHE" = false ] && [ -f "$CACHE_FILE" ]; then local cache_age=$(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0))) - [ $cache_age -lt $CACHE_MAX_AGE ] && cat "$CACHE_FILE" && return + debug "Cache age: ${cache_age}s (max: ${CACHE_MAX_AGE}s)" + if [ $cache_age -lt $CACHE_MAX_AGE ]; then + debug "Using cached repos" + cat "$CACHE_FILE" + return + fi + debug "Cache expired, rescanning..." fi + debug "Scanning for git repositories in $DEV_DIR..." if command -v fd >/dev/null 2>&1; then + debug "Using fd for scanning" fd -H -t d "^\.git$" "$DEV_DIR" -d 3 -0 | xargs -0 -n1 dirname | sort -u >"$CACHE_FILE" else + debug "Using find for scanning" find "$DEV_DIR" -maxdepth 3 -type d -name ".git" -print0 | xargs -0 -n1 dirname | sort -u >"$CACHE_FILE" fi + local repo_count=$(wc -l < "$CACHE_FILE") + debug "Found $repo_count repositories" cat "$CACHE_FILE" } @@ -106,18 +132,27 @@ sort_by_mru() { # Main execution REPOS=$(find_git_repos) -[ -z "$REPOS" ] && notify-send "Dev Launcher" "No git repositories found in $DEV_DIR" 2>/dev/null && exit 1 +if [ -z "$REPOS" ]; then + debug "No repositories found!" + notify-send "Dev Launcher" "No git repositories found in $DEV_DIR" 2>/dev/null || true + exit 1 +fi +debug "Found repositories, proceeding..." # Build display list (relative paths) and mapping DISPLAY_LIST=$(echo "$REPOS" | sed "s|^${DEV_DIR}/||") declare -A DISPLAY_TO_PATH +debug "Building display mapping..." while IFS=$'\t' read -r repo display; do DISPLAY_TO_PATH["$display"]="$repo" done < <(paste <(echo "$REPOS") <(echo "$DISPLAY_LIST")) +debug "Mapped ${#DISPLAY_TO_PATH[@]} projects" # Sort by MRU and present in tofi +debug "Sorting by MRU..." SORTED_LIST=$(sort_by_mru "$DISPLAY_LIST") +debug "Presenting in tofi..." SELECTED_DISPLAY=$(echo "$SORTED_LIST" | tofi \ --prompt-text "💀 Poison: " \ --fuzzy-match true \ @@ -139,21 +174,31 @@ SELECTED_DISPLAY=$(echo "$SORTED_LIST" | tofi \ --border-color '#31748f' \ --prompt-color '#f6c177') -[ -z "$SELECTED_DISPLAY" ] && exit 0 +debug "Selected display: '$SELECTED_DISPLAY'" +[ -z "$SELECTED_DISPLAY" ] && debug "No selection, exiting" && exit 0 # Remove star indicator and lookup path SELECTED_DISPLAY_CLEAN=$(echo "$SELECTED_DISPLAY" | sed 's/^⭐ //') SELECTED="${DISPLAY_TO_PATH[$SELECTED_DISPLAY_CLEAN]:-}" -[ -z "$SELECTED" ] || [ ! -d "$SELECTED" ] && exit 0 +debug "Selected display (clean): '$SELECTED_DISPLAY_CLEAN'" +debug "Selected path: '$SELECTED'" + +[ -z "$SELECTED" ] || [ ! -d "$SELECTED" ] && debug "Invalid selection, exiting" && exit 0 # Update MRU and launch +debug "Updating MRU..." update_mru "$SELECTED_DISPLAY_CLEAN" PROJECT_NAME=$(get_project_name "$SELECTED") SESSION_NAME="dev-${PROJECT_NAME}" CLASS_NAME="com.mzunino.dev.${PROJECT_NAME}" +debug "Project name: $PROJECT_NAME" +debug "Session name: $SESSION_NAME" +debug "Class name: $CLASS_NAME" +debug "Launching ghostty with launch-or-focus..." + launch-or-focus ghostty \ --working-directory="$SELECTED" \ --title="$PROJECT_NAME" \ diff --git a/local-bin/.local/bin/launch-or-focus b/local-bin/.local/bin/launch-or-focus index aeb0560..720abf3 100755 --- a/local-bin/.local/bin/launch-or-focus +++ b/local-bin/.local/bin/launch-or-focus @@ -3,6 +3,13 @@ set -euo pipefail # Script to launch an application or focus it if already running in sway # Usage: launch-or-focus [args...] +# Set DEBUG=1 to enable debug logging + +DEBUG="${DEBUG:-0}" + +debug() { + [ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2 || true +} if [ $# -eq 0 ]; then echo "Usage: $0 [args...]" >&2 @@ -14,6 +21,9 @@ shift ARGS=("$@") APP_NAME=$(basename "$COMMAND") +debug "Command: $COMMAND" +debug "App name: $APP_NAME" +debug "Args: ${ARGS[*]}" # Extract --class and --title values from args if present # Handle both --class VALUE and --class=VALUE formats @@ -22,25 +32,35 @@ TITLE_NAME="" for i in "${!ARGS[@]}"; do if [[ "${ARGS[$i]}" =~ ^--class=(.+)$ ]]; then CLASS_NAME="${BASH_REMATCH[1]}" + debug "Found class (format --class=VALUE): $CLASS_NAME" elif [ "${ARGS[$i]}" = "--class" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then CLASS_NAME="${ARGS[$((i+1))]}" + debug "Found class (format --class VALUE): $CLASS_NAME" elif [[ "${ARGS[$i]}" =~ ^--title=(.+)$ ]]; then TITLE_NAME="${BASH_REMATCH[1]}" + debug "Found title (format --title=VALUE): $TITLE_NAME" elif [ "${ARGS[$i]}" = "--title" ] && [ $((i+1)) -lt ${#ARGS[@]} ]; then TITLE_NAME="${ARGS[$((i+1))]}" + debug "Found title (format --title VALUE): $TITLE_NAME" fi done +debug "Extracted CLASS_NAME: '$CLASS_NAME'" +debug "Extracted TITLE_NAME: '$TITLE_NAME'" find_window_by_class() { local class="$1" - swaymsg -t get_tree | jq -r --arg class "$class" ' + debug "Searching for window by class: $class" + local result + result=$(swaymsg -t get_tree | jq -r --arg class "$class" ' recurse(.nodes[]?, .floating_nodes[]?) | select( ((.window_properties.class | type) == "string" and .window_properties.class == $class) or ((.app_id | type) == "string" and .app_id == $class) ) | .id - ' | head -n 1 + ' | head -n 1) + debug "find_window_by_class result: '$result'" + echo "$result" } find_window_by_title() { @@ -48,6 +68,8 @@ find_window_by_title() { local app="$2" local result + debug "Searching for window by title: '$title' and app: '$app'" + # Search for windows matching both app and title (most specific) result=$(swaymsg -t get_tree | jq -r --arg title "$title" --arg app "$app" ' recurse(.nodes[]?, .floating_nodes[]?) | @@ -61,28 +83,36 @@ find_window_by_title() { .id ' | head -n 1) + debug "find_window_by_title (app+title) result: '$result'" [ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return # Fall back to title-only exact match + debug "Falling back to title-only exact match" result=$(swaymsg -t get_tree | jq -r --arg title "$title" ' recurse(.nodes[]?, .floating_nodes[]?) | select((.name | type) == "string" and .name == $title) | .id ' | head -n 1) + debug "find_window_by_title (title only) result: '$result'" [ -n "$result" ] && [ "$result" != "null" ] && echo "$result" && return # Final fallback: title-only case-insensitive match - swaymsg -t get_tree | jq -r --arg title "$title" ' + debug "Falling back to title-only case-insensitive match" + result=$(swaymsg -t get_tree | jq -r --arg title "$title" ' recurse(.nodes[]?, .floating_nodes[]?) | select((.name | type) == "string" and (.name | test($title; "i"))) | .id - ' | head -n 1 + ' | head -n 1) + debug "find_window_by_title (case-insensitive) result: '$result'" + echo "$result" } find_window() { local app_name="$1" - swaymsg -t get_tree | jq -r --arg app "$app_name" ' + debug "Searching for window by app name: $app_name" + local result + result=$(swaymsg -t get_tree | jq -r --arg app "$app_name" ' recurse(.nodes[]?, .floating_nodes[]?) | select( ((.app_id | type) == "string" and (.app_id == $app or (.app_id | test($app; "i")))) or @@ -90,22 +120,48 @@ find_window() { ((.name | type) == "string" and (.name | test($app; "i"))) ) | .id - ' | head -n 1 + ' | head -n 1) + debug "find_window result: '$result'" + echo "$result" } focus_window() { local window_id="$1" + debug "Focusing window ID: $window_id" swaymsg "[con_id=$window_id]" focus } WINDOW_ID="" -[ -n "$CLASS_NAME" ] && WINDOW_ID=$(find_window_by_class "$CLASS_NAME") -([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -n "$TITLE_NAME" ] && WINDOW_ID=$(find_window_by_title "$TITLE_NAME" "$APP_NAME") -([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "$APP_NAME") -([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "${APP_NAME,,}") +debug "Starting window search..." +# Only search by class/title if provided - don't fall back to generic app search +# This prevents matching the wrong window +if [ -n "$CLASS_NAME" ]; then + WINDOW_ID=$(find_window_by_class "$CLASS_NAME") + debug "After class search: WINDOW_ID='$WINDOW_ID'" +fi -[ -n "$WINDOW_ID" ] && [ "$WINDOW_ID" != "null" ] && [ "$WINDOW_ID" -eq "$WINDOW_ID" ] 2>/dev/null && focus_window "$WINDOW_ID" && exit 0 +if ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -n "$TITLE_NAME" ]; then + WINDOW_ID=$(find_window_by_title "$TITLE_NAME" "$APP_NAME") + debug "After title search: WINDOW_ID='$WINDOW_ID'" +fi +# Only fall back to generic app search if we don't have class or title +# This means the user wants to focus any instance of the app +if ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && [ -z "$CLASS_NAME" ] && [ -z "$TITLE_NAME" ]; then + WINDOW_ID=$(find_window "$APP_NAME") + debug "After app name search (no class/title): WINDOW_ID='$WINDOW_ID'" + + ([ -z "$WINDOW_ID" ] || [ "$WINDOW_ID" = "null" ]) && WINDOW_ID=$(find_window "${APP_NAME,,}") + debug "After lowercase app name search: WINDOW_ID='$WINDOW_ID'" +fi + +if [ -n "$WINDOW_ID" ] && [ "$WINDOW_ID" != "null" ] && [ "$WINDOW_ID" -eq "$WINDOW_ID" ] 2>/dev/null; then + debug "Window found! Focusing window ID: $WINDOW_ID" + focus_window "$WINDOW_ID" + exit 0 +fi + +debug "No matching window found, launching new instance" "$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 & disown diff --git a/zsh/.config/zsh/functions/utils.zsh b/zsh/.config/zsh/functions/utils.zsh index 3295927..bdedc79 100644 --- a/zsh/.config/zsh/functions/utils.zsh +++ b/zsh/.config/zsh/functions/utils.zsh @@ -66,3 +66,8 @@ alias fml='font-manager list' alias fms='font-manager status' alias fmc='font-manager clean' alias fma='font-manager all' + +function mcd() { + mkdir -p "$1" && cd "$1" || return +} +compdef _cd mcd