#!/bin/sh # Usage function to display script options usage() { echo "Find files using ripgrep and open them in Neovim." echo "" echo "Usage: ${0##*/} [-s] [-i] [-l] [-p] [] " echo "" echo "Options:" echo " -h : Show this message" echo " -i : Perform a case-insensitive search (default)" echo " -l : List files associated with the given tag" echo " -p : Search for files in the specified project directories using the specified tag (default: PROJECT)" echo " -s : Perform a case-sensitive search" echo " [] : Optional tag for project mode, followed by the search query" echo "" echo "Examples:" echo " ${0##*/} -p TODO 'KEYWORD' # Search for 'KEYWORD' in files tagged with 'TODO' in the project directories" echo " ${0##*/} -l -p 'KEYWORD' # List files associated with the default 'PROJECT' tag and 'KEYWORD'" echo " ${0##*/} 'KEYWORD' # Open files containing 'KEYWORD' in nvim" exit 0 } search_term() { case_flag="$1" shift if ! command -v rga >/dev/null 2>&1; then echo "Error: 'rga' is not installed." >&2 exit 1 fi if ! command -v xclip >/dev/null 2>&1; then echo "Error: 'xclip' is not installed." >&2 exit 1 fi # Construct the preview command preview_cmd=$(printf "rga %s --pretty --context 10 '%s' {}" "$case_flag" "$*") query="$*" rga_output=$(rga --follow --no-ignore --hidden --text --max-count=1 ${case_flag:+$case_flag} --files-with-matches --no-messages \ --glob '!**/.cache/*' \ --glob '!**/.git/*' \ --glob '!**/.github/*' \ --glob '!**/.next/*' \ --glob '!**/.venv/*' \ --glob '!**/build/*' \ --glob '!**/coverage/*' \ --glob '!**/dist/*' \ --glob '!**/node_modules/*' \ --glob '!**/target/*' \ --glob '!**/vendor/*' \ --glob '!**/venv/*' \ "$query") # Use fzf to select files files=$(echo "$rga_output" | fzf-tmux +m --preview="$preview_cmd" --reverse --multi --select-1 --exit-0) || return 1 # Check if files are selected if [ -z "$files" ]; then echo "No files selected." return 0 fi # copy target to the clipboard echo "$query" | xclip -selection clipboard 2>/dev/null # Open files at matching lines count=$(echo "$files" | wc -l) if [ "$count" -eq 1 ]; then # Find the first matching line number in the selected file line=$(rga --max-count=1 --line-number --no-filename ${case_flag:+$case_flag} --no-messages "$query" "$files" 2>/dev/null | head -1 | cut -d: -f1) if [ -n "$line" ]; then ${EDITOR:-nvim} "+${line}" "$(realpath "$files")" else openfiles "$files" fi else # Multiple files: open at matching lines via quickfix tmpfile=$(mktemp) echo "$files" | while IFS= read -r file; do match=$(rga --max-count=1 --line-number --no-filename ${case_flag:+$case_flag} --no-messages "$query" "$file" 2>/dev/null | head -1) if [ -n "$match" ]; then printf '%s:%s\n' "$(realpath "$file")" "$match" >> "$tmpfile" else printf '%s:1:\n' "$(realpath "$file")" >> "$tmpfile" fi done if [ "$count" -lt 5 ]; then ${EDITOR:-nvim} -q "$tmpfile" \ -c 'for i in range(2,len(getqflist())) | vsplit | execute "cc ".i | endfor | wincmd t' else first_line=$(head -1 "$tmpfile" | cut -d: -f2) file_args="" while IFS=: read -r fpath _; do file_args="$file_args \"$fpath\"" done < "$tmpfile" eval "${EDITOR:-nvim}" "+${first_line}" "$file_args" fi rm -f "$tmpfile" fi # print the file names echo "$rga_output" } # Function to list and/or open all files associated with a given project tag list_or_open_project_files() { # Use the provided tag or default to "PROJECT" project_tag="${1:-PROJECT}: $2" # Define the project paths as a space-separated string project_paths="$HOME/.dotfiles $HOME/.local/src/suckless $HOME/Public/repos" # Use rga to find files containing the project tag across all project paths rga_output="" for path in $project_paths; do if [ -d "$path" ]; then rga_result=$(rga --follow --no-ignore --hidden --text --max-count=1 --files-with-matches --no-messages \ --glob '!**/.cache/*' \ --glob '!**/.git/*' \ --glob '!**/.github/*' \ --glob '!**/.next/*' \ --glob '!**/.venv/*' \ --glob '!**/build/*' \ --glob '!**/coverage/*' \ --glob '!**/dist/*' \ --glob '!**/node_modules/*' \ --glob '!**/target/*' \ --glob '!**/vendor/*' \ --glob '!**/venv/*' \ "$project_tag" "$path") rga_output="$rga_output $rga_result" fi done # Remove leading/trailing whitespace rga_output=$(echo "$rga_output" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') # Check if any files were found if [ -z "$rga_output" ]; then echo "No files found for tag $project_tag." return 0 fi # If the script was called in list mode, simply print the files if [ "$list_mode" -eq 1 ]; then echo "$rga_output" else # Open files at matching lines file_count=$(echo "$rga_output" | wc -w) if [ "$file_count" -eq 1 ]; then line=$(rga --max-count=1 --line-number --no-filename --no-messages "$project_tag" "$rga_output" 2>/dev/null | head -1 | cut -d: -f1) if [ -n "$line" ]; then ${EDITOR:-nvim} "+${line}" "$(realpath "$rga_output")" else openfiles "$rga_output" fi else # Multiple files: open at matching lines via quickfix tmpfile=$(mktemp) for file in $rga_output; do match=$(rga --max-count=1 --line-number --no-filename --no-messages "$project_tag" "$file" 2>/dev/null | head -1) if [ -n "$match" ]; then printf '%s:%s\n' "$(realpath "$file")" "$match" >> "$tmpfile" else printf '%s:1:\n' "$(realpath "$file")" >> "$tmpfile" fi done if [ "$file_count" -lt 5 ]; then ${EDITOR:-nvim} -q "$tmpfile" \ -c 'for i in range(2,len(getqflist())) | vsplit | execute "cc ".i | endfor | wincmd t' else ${EDITOR:-nvim} -q "$tmpfile" \ -c 'silent! for i in range(2,len(getqflist())+1) | execute "silent! cc ".i | endfor | silent! cfirst' fi rm -f "$tmpfile" fi fi } # Main function to handle options case_flag="--ignore-case" # Default to case-insensitive list_mode=0 project_mode=0 # Parse the options while getopts "silph" opt; do case $opt in s) case_flag="--case-sensitive" ;; # Case-sensitive i) case_flag="--ignore-case" ;; # Case-insensitive l) list_mode=1 ;; # List mode p) project_mode=1 ;; # Project mode h) usage ;; *) ;; esac done shift $((OPTIND - 1)) # Handle project mode search if [ "$project_mode" -eq 1 ]; then list_or_open_project_files "$1" "$2" else # Otherwise, call the common search function search_term "$case_flag" "$@" fi