main dotfiles / bin / ctags-project
Eric Bower  ·  2026-09-16
 1#!/usr/bin/env bash
 2set -euo pipefail
 3
 4if ! command -v ctags >/dev/null 2>&1; then
 5  echo "error: ctags not found in PATH" >&2
 6  exit 1
 7fi
 8
 9if ! command -v fd >/dev/null 2>&1; then
10  echo "error: fd not found in PATH" >&2
11  exit 1
12fi
13
14outfile=""
15paths=()
16while [[ $# -gt 0 ]]; do
17  case "$1" in
18    -f) outfile="$2"; shift 2 ;;
19    *) paths+=("$1"); shift ;;
20  esac
21done
22
23if [[ -z "$outfile" ]]; then
24  echo "error: -f <outfile> is required" >&2
25  exit 1
26fi
27
28[[ ${#paths[@]} -eq 0 ]] && paths=(".")
29
30# .tsx isn't mapped to the TypeScript parser by default in Universal Ctags,
31# so every .tsx component would otherwise go untagged.
32ctags_flags=(--map-TypeScript=+.tsx --fields=+S -f "$outfile")
33
34# A single-file target (e.g. per-file tag update on save) is tagged directly.
35if [[ ${#paths[@]} -eq 1 && -f "${paths[0]}" ]]; then
36  exec ctags "${ctags_flags[@]}" "${paths[0]}"
37fi
38
39# Directory targets: fd respects .gitignore, so build output, dependency
40# dirs, and caches (which can hold single lines many MB long, e.g. turbo
41# manifests) never get scanned and bloat the tags file.
42fd -t f -H --no-require-git -E .git . "${paths[@]}" | ctags "${ctags_flags[@]}" -L -