dev: automated commit - 2025-11-28 18:32:11
This commit is contained in:
parent
023988b638
commit
d6e088278f
3 changed files with 399 additions and 0 deletions
272
local-bin/.local/bin/niri-dev-launcher
Executable file
272
local-bin/.local/bin/niri-dev-launcher
Executable file
|
|
@ -0,0 +1,272 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CACHE_MAX_AGE=300
|
||||||
|
MRU_SIZE=5
|
||||||
|
|
||||||
|
# Debug logging (set DEBUG=1 to enable via environment variable)
|
||||||
|
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 niri-dev-launcher with args: $*"
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--help | -h)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [DEV_DIR] [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--no-cache Bypass cache and rescan repositories
|
||||||
|
--clear-cache Clear cache and MRU files
|
||||||
|
--help, -h Show this help message
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--no-cache)
|
||||||
|
NO_CACHE=true
|
||||||
|
;;
|
||||||
|
--clear-cache)
|
||||||
|
CLEAR_CACHE=true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[ -z "$DEV_DIR" ] && DEV_DIR="$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
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/niri-dev-launcher-cache-${DEV_DIR_HASH}"
|
||||||
|
MRU_FILE="$HOME/.cache/niri-dev-launcher-mru-${DEV_DIR_HASH}"
|
||||||
|
debug "CACHE_FILE: $CACHE_FILE"
|
||||||
|
debug "MRU_FILE: $MRU_FILE"
|
||||||
|
|
||||||
|
# Handle --clear-cache
|
||||||
|
if [ "$CLEAR_CACHE" = true ]; then
|
||||||
|
rm -f "$CACHE_FILE" "$MRU_FILE"
|
||||||
|
notify-send "Niri Dev Launcher" "Cache cleared" 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
|
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)))
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get sanitized project name for tmux session
|
||||||
|
get_project_name() {
|
||||||
|
basename "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update MRU list
|
||||||
|
update_mru() {
|
||||||
|
local selected="$1"
|
||||||
|
local temp_file
|
||||||
|
temp_file=$(mktemp)
|
||||||
|
|
||||||
|
echo "$selected" >"$temp_file"
|
||||||
|
[ -f "$MRU_FILE" ] && grep -vFx "$selected" "$MRU_FILE" >>"$temp_file" || true
|
||||||
|
head -n "$MRU_SIZE" "$temp_file" >"$MRU_FILE"
|
||||||
|
rm -f "$temp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort by MRU (MRU items first with ⭐, then others)
|
||||||
|
sort_by_mru() {
|
||||||
|
local all_repos="$1"
|
||||||
|
local mru_list
|
||||||
|
local temp_file
|
||||||
|
|
||||||
|
[ -f "$MRU_FILE" ] && mru_list=$(grep -v '^$' "$MRU_FILE") || mru_list=""
|
||||||
|
|
||||||
|
# If MRU list is empty, just return sorted repos
|
||||||
|
if [ -z "$mru_list" ]; then
|
||||||
|
echo "$all_repos" | sort
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output MRU items first with indicator
|
||||||
|
echo "$mru_list" | sed 's/^/⭐ /'
|
||||||
|
|
||||||
|
# Output non-MRU items using comm (fast set difference)
|
||||||
|
temp_file=$(mktemp)
|
||||||
|
echo "$all_repos" | sort >"$temp_file.all"
|
||||||
|
echo "$mru_list" | sort >"$temp_file.mru"
|
||||||
|
comm -23 "$temp_file.all" "$temp_file.mru" 2>/dev/null || cat "$temp_file.all"
|
||||||
|
rm -f "$temp_file.all" "$temp_file.mru"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find window by app-id in niri
|
||||||
|
find_window_by_app_id() {
|
||||||
|
local app_id="$1"
|
||||||
|
debug "Searching for window with app-id: $app_id"
|
||||||
|
|
||||||
|
# Check if jq is available
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
debug "jq not available, falling back to grep method"
|
||||||
|
# Get windows list and search for matching app-id
|
||||||
|
niri msg windows 2>/dev/null | grep -A 1 "App ID: \"$app_id\"" | head -1 | grep -q "Window ID" || return 1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get windows list from niri as JSON
|
||||||
|
local windows_json
|
||||||
|
windows_json=$(niri msg -j windows 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
if [ -z "$windows_json" ] || [ "$windows_json" = "[]" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use jq to find matching window
|
||||||
|
local window_id
|
||||||
|
window_id=$(echo "$windows_json" | jq -r --arg app_id "$app_id" '
|
||||||
|
.[] | select(.app_id | ascii_downcase == ($app_id | ascii_downcase)) | .id
|
||||||
|
' | head -1)
|
||||||
|
|
||||||
|
if [ -n "$window_id" ] && [ "$window_id" != "null" ]; then
|
||||||
|
echo "$window_id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Focus or launch alacritty window
|
||||||
|
focus_or_launch_alacritty() {
|
||||||
|
local class_name="$1"
|
||||||
|
local title="$2"
|
||||||
|
local working_dir="$3"
|
||||||
|
local session_name="$4"
|
||||||
|
|
||||||
|
debug "focus_or_launch_alacritty: class=$class_name, title=$title, dir=$working_dir"
|
||||||
|
|
||||||
|
# Try to find existing window
|
||||||
|
local window_id
|
||||||
|
window_id=$(find_window_by_app_id "$class_name" || echo "")
|
||||||
|
|
||||||
|
if [ -n "$window_id" ]; then
|
||||||
|
debug "Found existing window, focusing..."
|
||||||
|
niri msg action focus-window --id "$window_id" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
debug "No existing window found, launching alacritty..."
|
||||||
|
alacritty \
|
||||||
|
--class="$class_name" \
|
||||||
|
--title="$title" \
|
||||||
|
--working-directory="$working_dir" \
|
||||||
|
-e bash -c "if tmux has-session -t '$session_name' 2>/dev/null; then \
|
||||||
|
tmux attach -t '$session_name'; \
|
||||||
|
else \
|
||||||
|
tmux new-session -c '$working_dir' -s '$session_name' -d '$EDITOR'; \
|
||||||
|
tmux split-window -h -c '$working_dir' -t '$session_name'; \
|
||||||
|
tmux select-pane -t '$session_name:0.0'; \
|
||||||
|
tmux attach -t '$session_name'; \
|
||||||
|
fi" >/dev/null 2>&1 &
|
||||||
|
disown
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
REPOS=$(find_git_repos)
|
||||||
|
|
||||||
|
if [ -z "$REPOS" ]; then
|
||||||
|
debug "No repositories found!"
|
||||||
|
notify-send "Niri 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 fuzzel
|
||||||
|
debug "Sorting by MRU..."
|
||||||
|
SORTED_LIST=$(sort_by_mru "$DISPLAY_LIST")
|
||||||
|
debug "Presenting in fuzzel..."
|
||||||
|
|
||||||
|
# Configure fuzzel with similar style to original tofi
|
||||||
|
SELECTED_DISPLAY=$(echo "$SORTED_LIST" | fuzzel \
|
||||||
|
--prompt "💀 Poison: " \
|
||||||
|
--dmenu \
|
||||||
|
--width 30 \
|
||||||
|
--lines 20 \
|
||||||
|
--border-width 2 \
|
||||||
|
--font "$(fc-match -f '%{family}:size=16' 2>/dev/null || echo 'sans:size=16')" \
|
||||||
|
--background-color '#191724ff' \
|
||||||
|
--text-color '#e0def4ff' \
|
||||||
|
--match-color '#31748fff' \
|
||||||
|
--selection-color '#1f1d2eff' \
|
||||||
|
--selection-text-color '#31748fff' \
|
||||||
|
--selection-match-color '#31748fff' \
|
||||||
|
--prompt-color '#f6c177ff')
|
||||||
|
|
||||||
|
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]:-}"
|
||||||
|
|
||||||
|
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 "Focusing or launching alacritty..."
|
||||||
|
|
||||||
|
focus_or_launch_alacritty "$CLASS_NAME" "$PROJECT_NAME" "$SELECTED" "$SESSION_NAME"
|
||||||
109
local-bin/.local/bin/niri-launch-or-focus
Executable file
109
local-bin/.local/bin/niri-launch-or-focus
Executable file
|
|
@ -0,0 +1,109 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Simple script to launch an application or focus it if already running in niri
|
||||||
|
# Usage: niri-launch-or-focus <command> [--class CLASS] [--title TITLE] [args...]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# niri-launch-or-focus firefox
|
||||||
|
# niri-launch-or-focus code --class Code --title "My Project"
|
||||||
|
# niri-launch-or-focus kitty --class kitty
|
||||||
|
|
||||||
|
CLASS=""
|
||||||
|
TITLE=""
|
||||||
|
ARGS=()
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--class)
|
||||||
|
CLASS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--title)
|
||||||
|
TITLE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ARGS+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#ARGS[@]} -eq 0 ]; then
|
||||||
|
echo "Usage: $0 <command> [--class CLASS] [--title TITLE] [args...]" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMAND="${ARGS[0]}"
|
||||||
|
APP_NAME=$(basename "$COMMAND")
|
||||||
|
unset 'ARGS[0]'
|
||||||
|
ARGS=("${ARGS[@]}")
|
||||||
|
|
||||||
|
# If class/title were provided as flags, add them to command args (for apps that support them)
|
||||||
|
if [ -n "$CLASS" ]; then
|
||||||
|
ARGS=("--class=$CLASS" "${ARGS[@]}")
|
||||||
|
fi
|
||||||
|
if [ -n "$TITLE" ]; then
|
||||||
|
ARGS=("--title=$TITLE" "${ARGS[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find window by app_id (niri uses app-id), title, or app name
|
||||||
|
find_window() {
|
||||||
|
local class="$1"
|
||||||
|
local title="$2"
|
||||||
|
local app="$3"
|
||||||
|
|
||||||
|
# Check if jq is available
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "Error: jq is required but not found. Please install jq." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get windows list from niri as JSON
|
||||||
|
local windows_json
|
||||||
|
windows_json=$(niri msg -j windows 2>/dev/null || echo "[]")
|
||||||
|
|
||||||
|
if [ -z "$windows_json" ] || [ "$windows_json" = "[]" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use jq to find matching window
|
||||||
|
local window_id
|
||||||
|
|
||||||
|
if [ -n "$class" ]; then
|
||||||
|
# Match by class (app_id)
|
||||||
|
window_id=$(echo "$windows_json" | jq -r --arg class "$class" '
|
||||||
|
.[] | select(.app_id | ascii_downcase == ($class | ascii_downcase)) | .id
|
||||||
|
' | head -1)
|
||||||
|
elif [ -n "$title" ]; then
|
||||||
|
# Match by title
|
||||||
|
window_id=$(echo "$windows_json" | jq -r --arg title "$title" '
|
||||||
|
.[] | select(.title == $title) | .id
|
||||||
|
' | head -1)
|
||||||
|
else
|
||||||
|
# Match by app name (basename of command)
|
||||||
|
window_id=$(echo "$windows_json" | jq -r --arg app "$app" '
|
||||||
|
.[] | select(.app_id | ascii_downcase == ($app | ascii_downcase)) | .id
|
||||||
|
' | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$window_id" ] && [ "$window_id" != "null" ]; then
|
||||||
|
echo "$window_id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try to find existing window
|
||||||
|
WINDOW_ID=$(find_window "$CLASS" "$TITLE" "$APP_NAME" || echo "")
|
||||||
|
|
||||||
|
# Focus if found, otherwise launch
|
||||||
|
if [ -n "$WINDOW_ID" ]; then
|
||||||
|
niri msg action focus-window --id "$WINDOW_ID" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
"$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 &
|
||||||
|
disown
|
||||||
|
fi
|
||||||
18
local-bin/.local/bin/niri-scratchpad.sh
Executable file
18
local-bin/.local/bin/niri-scratchpad.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/run/current-system/sw/bin/bash
|
||||||
|
|
||||||
|
# Check if scratchpad terminal is already running
|
||||||
|
if pgrep -f "kitty.*scratchpad" > /dev/null; then
|
||||||
|
# Get the window ID of the scratchpad terminal
|
||||||
|
WINDOW_ID=$(niri msg windows | grep -A 10 'app-id="scratchpad"' | head -1 | grep 'Window ID' | cut -d' ' -f3 | cut -d: -f1)
|
||||||
|
|
||||||
|
if [ -n "$WINDOW_ID" ]; then
|
||||||
|
# Focus the existing scratchpad window
|
||||||
|
niri msg action focus-window "$WINDOW_ID"
|
||||||
|
else
|
||||||
|
# If we can't find the window ID, spawn a new one
|
||||||
|
kitty --class scratchpad --title scratchpad &
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Spawn a new scratchpad terminal
|
||||||
|
kitty --class scratchpad --title scratchpad &
|
||||||
|
fi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue