Commit 92fc4b6

Eric Bower  ·  2026-09-08 21:52:30 -0400 EDT
parent 643b832
feat: ctag lang support
7 files changed,  +313, -31
+107, -0
......@@ -0,0 +1,107 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+if ! command -v ctags >/dev/null 2>&1; then
5+ echo "error: ctags not found in PATH" >&2
6+ exit 1
7+fi
8+
9+target=".tags_deps"
10+generated=0
11+
12+# 1. Go modules
13+if [[ -f "go.mod" ]] && command -v go >/dev/null 2>&1; then
14+ echo "Resolving Go dependencies..."
15+ dirs="$(go list -m -f '{{if not .Main}}{{.Dir}}{{end}}' all 2>/dev/null | grep -v '^$' || true)"
16+ if [[ -n "${dirs}" ]]; then
17+ echo "Indexing Go dependencies into ${target}..."
18+ flags=("-f" "${target}")
19+ [[ $generated -eq 1 ]] && flags+=("-a")
20+ echo "${dirs}" | ctags "${flags[@]}" -L -
21+ generated=1
22+ fi
23+fi
24+
25+# 2. Zig dependencies (build.zig.zon)
26+if [[ -f "build.zig.zon" ]]; then
27+ echo "Resolving Zig dependencies..."
28+ cache_p="${ZIG_GLOBAL_CACHE_DIR:-$HOME/.cache/zig}/p"
29+ zig_dirs=()
30+ if [[ -d "${cache_p}" ]]; then
31+ while IFS= read -r hash; do
32+ [[ -z "$hash" ]] && continue
33+ for d in "${cache_p}"/*"${hash}"*; do
34+ if [[ -d "$d" ]]; then
35+ zig_dirs+=("$d")
36+ fi
37+ done
38+ done < <(grep -oE '[0-9a-zA-Z_-]{40,}' build.zig.zon | sort -u)
39+ fi
40+
41+ if [[ -d ".zig-cache/p" ]]; then
42+ for d in .zig-cache/p/*; do
43+ [[ -d "$d" ]] && zig_dirs+=("$d")
44+ done
45+ fi
46+
47+ if [[ ${#zig_dirs[@]} -gt 0 ]]; then
48+ echo "Indexing ${#zig_dirs[@]} Zig dependencies into ${target}..."
49+ flags=("-f" "${target}")
50+ [[ $generated -eq 1 ]] && flags+=("-a")
51+ printf "%s\n" "${zig_dirs[@]}" | ctags "${flags[@]}" -L -
52+ generated=1
53+ fi
54+fi
55+
56+# 3. Python virtualenv
57+venv_dir=""
58+if [[ -n "${VIRTUAL_ENV:-}" && -d "${VIRTUAL_ENV}" ]]; then
59+ venv_dir="${VIRTUAL_ENV}"
60+elif [[ -d ".venv" ]]; then
61+ venv_dir=".venv"
62+elif [[ -d "venv" ]]; then
63+ venv_dir="venv"
64+fi
65+
66+if [[ -n "${venv_dir}" ]]; then
67+ echo "Resolving Python dependencies from ${venv_dir}..."
68+ sp_dirs=()
69+ for sp in "${venv_dir}"/lib/python*/site-packages; do
70+ [[ -d "$sp" ]] && sp_dirs+=("$sp")
71+ done
72+
73+ if [[ ${#sp_dirs[@]} -gt 0 ]]; then
74+ echo "Indexing Python site-packages into ${target}..."
75+ flags=("-f" "${target}" "--exclude=*.dist-info" "--exclude=*.egg-info" "--exclude=*/tests/*" "--exclude=*/test/*")
76+ [[ $generated -eq 1 ]] && flags+=("-a")
77+ ctags "${flags[@]}" "${sp_dirs[@]}"
78+ generated=1
79+ fi
80+fi
81+
82+# 4. TypeScript / Node.js
83+if [[ -f "package.json" && -d "node_modules" ]]; then
84+ echo "Resolving TypeScript / Node dependencies..."
85+ ts_targets=()
86+ if [[ -d "node_modules/@types" ]]; then
87+ ts_targets+=("node_modules/@types")
88+ fi
89+ while IFS= read -r dts; do
90+ [[ -f "$dts" ]] && ts_targets+=("$dts")
91+ done < <(find node_modules -maxdepth 4 -name "*.d.ts" ! -path "*/node_modules/@types/*" 2>/dev/null | head -n 500)
92+
93+ if [[ ${#ts_targets[@]} -gt 0 ]]; then
94+ echo "Indexing TypeScript types into ${target}..."
95+ flags=("-f" "${target}")
96+ [[ $generated -eq 1 ]] && flags+=("-a")
97+ printf "%s\n" "${ts_targets[@]}" | ctags "${flags[@]}" -L -
98+ generated=1
99+ fi
100+fi
101+
102+if [[ $generated -eq 1 ]]; then
103+ echo "Saved dependency tags to ${target} ($(du -h "${target}" | cut -f1))"
104+else
105+ echo "No third-party dependencies detected in current directory."
106+ exit 1
107+fi
+29, -0
......@@ -0,0 +1,29 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+if ! command -v go >/dev/null 2>&1; then
5+ echo "error: go not found in PATH" >&2
6+ exit 1
7+fi
8+
9+if ! command -v ctags >/dev/null 2>&1; then
10+ echo "error: ctags not found in PATH" >&2
11+ exit 1
12+fi
13+
14+goroot="$(go env GOROOT)"
15+std_dir="${goroot}/src"
16+
17+if [[ -z "${goroot}" || ! -d "${std_dir}" ]]; then
18+ echo "error: could not locate go std directory" >&2
19+ exit 1
20+fi
21+
22+cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/tags"
23+target="${cache_dir}/go_std.tags"
24+
25+mkdir -p "${cache_dir}"
26+
27+echo "Generating Go standard library tags from ${std_dir}..."
28+ctags -f "${target}" "${std_dir}"
29+echo "Saved tags to ${target} ($(du -h "${target}" | cut -f1))"
+67, -27
......@@ -1,33 +1,73 @@
11 #!/usr/bin/env bash
22 file="$1"
33
4-awk '
5- /^(pub )?const [a-zA-Z0-9_]+ = (struct|enum|union|opaque)/ {
6- match($0, /const ([a-zA-Z0-9_]+)/, m)
7- curr_struct = m[1]
8- }
9- /^};?/ {
10- curr_struct = ""
11- }
12- /(pub |inline |export |extern )*fn [a-zA-Z0-9_]+/ {
13- match($0, /fn ([a-zA-Z0-9_]+)/, m)
14- fn_name = m[1]
15- if (fn_name != "") {
16- recv = curr_struct
17- if (recv == "" && match($0, /\((self|it): \*?([a-zA-Z0-9_]+)/, r)) {
18- recv = r[2]
4+if [[ ! -f "$file" ]]; then
5+ echo "echo -markup \"{Error}File not found\""
6+ exit 0
7+fi
8+
9+ext="${file##*.}"
10+
11+if [[ "$ext" == "zig" ]]; then
12+ awk '
13+ /^(pub )?const [a-zA-Z0-9_]+ = (struct|enum|union|opaque)/ {
14+ match($0, /const ([a-zA-Z0-9_]+)/, m)
15+ curr_struct = m[1]
16+ }
17+ /^};?/ {
18+ curr_struct = ""
19+ }
20+ /(pub |inline |export |extern )*fn [a-zA-Z0-9_]+/ {
21+ match($0, /fn ([a-zA-Z0-9_]+)/, m)
22+ fn_name = m[1]
23+ if (fn_name != "") {
24+ recv = curr_struct
25+ if (recv == "" && match($0, /\((self|it): \*?([a-zA-Z0-9_]+)/, r)) {
26+ recv = r[2]
27+ }
28+ label = (recv != "" ? recv " > " fn_name : fn_name) " : " NR
29+ gsub("!", "!!", label)
30+ action = "execute-keys %|" NR "gvc|"
31+ out = out "%!" label "! %!evaluate-commands %# try %& " action " & # !"
32+ }
33+ }
34+ END {
35+ if (length(out) == 0) {
36+ print "echo -markup \"{Error}No functions found in current file\""
37+ } else {
38+ print "menu " out
1939 }
20- label = (recv != "" ? recv " > " fn_name : fn_name) " : " NR
21- gsub("!", "!!", label)
22- action = "execute-keys %|" NR "gvc|"
23- out = out "%!" label "! %!evaluate-commands %# try %& " action " & # !"
2440 }
25- }
26- END {
27- if (length(out) == 0) {
28- print "echo -markup \"{Error}No functions found in current file\""
29- } else {
30- print "menu " out
41+ ' "$file"
42+else
43+ ctags -f - --sort=no --fields=+nK "$file" 2>/dev/null | awk -F'\t' '
44+ {
45+ name = $1
46+ kind = $4
47+ line = $5
48+ sub(/^line:/, "", line)
49+ scope = ""
50+ for (i = 6; i <= NF; i++) {
51+ if ($i ~ /^(class|struct|interface|type):/) {
52+ scope = $i
53+ sub(/^[a-z]+:/, "", scope)
54+ sub(/.*[.]/, "", scope)
55+ }
56+ }
57+ if (kind ~ /^(func|function|method|member|methodSpec|constructor)$/) {
58+ label = (scope != "" ? scope " > " name : name) " : " line
59+ if (seen[line, label]++) next
60+ gsub("!", "!!", label)
61+ action = "execute-keys %|" line "gvc|"
62+ out = out "%!" label "! %!evaluate-commands %# try %& " action " & # !"
63+ }
64+ }
65+ END {
66+ if (length(out) == 0) {
67+ print "echo -markup \"{Error}No functions found in current file\""
68+ } else {
69+ print "menu " out
70+ }
3171 }
32- }
33-' "$file"
72+ '
73+fi
+32, -0
......@@ -0,0 +1,32 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+if ! command -v python3 >/dev/null 2>&1; then
5+ echo "error: python3 not found in PATH" >&2
6+ exit 1
7+fi
8+
9+if ! command -v ctags >/dev/null 2>&1; then
10+ echo "error: ctags not found in PATH" >&2
11+ exit 1
12+fi
13+
14+std_dir="$(python3 -c "import sysconfig; print(sysconfig.get_path('stdlib'))" 2>/dev/null || true)"
15+
16+if [[ -z "${std_dir}" || ! -d "${std_dir}" ]]; then
17+ echo "error: could not locate python std directory" >&2
18+ exit 1
19+fi
20+
21+cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/tags"
22+target="${cache_dir}/python_std.tags"
23+
24+mkdir -p "${cache_dir}"
25+
26+echo "Generating Python standard library tags from ${std_dir}..."
27+ctags --exclude="*/site-packages/*" \
28+ --exclude="*/test/*" \
29+ --exclude="*/tests/*" \
30+ --exclude="*/idlelib/*" \
31+ -f "${target}" "${std_dir}"
32+echo "Saved tags to ${target} ($(du -h "${target}" | cut -f1))"
+41, -0
......@@ -0,0 +1,41 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+if ! command -v ctags >/dev/null 2>&1; then
5+ echo "error: ctags not found in PATH" >&2
6+ exit 1
7+fi
8+
9+std_dir=""
10+if command -v tsc >/dev/null 2>&1; then
11+ tsc_real="$(realpath "$(command -v tsc)")"
12+ ts_dir="$(dirname "$tsc_real")"
13+ if [[ -d "${ts_dir}/../lib" ]]; then
14+ std_dir="$(realpath "${ts_dir}/../lib")"
15+ elif [[ -d "${ts_dir}/lib" ]]; then
16+ std_dir="$(realpath "${ts_dir}/lib")"
17+ fi
18+fi
19+
20+if [[ -z "${std_dir}" || ! -d "${std_dir}" ]]; then
21+ if command -v npm >/dev/null 2>&1; then
22+ npm_root="$(npm root -g 2>/dev/null || true)"
23+ if [[ -n "${npm_root}" && -d "${npm_root}/typescript/lib" ]]; then
24+ std_dir="${npm_root}/typescript/lib"
25+ fi
26+ fi
27+fi
28+
29+if [[ -z "${std_dir}" || ! -d "${std_dir}" ]]; then
30+ echo "error: could not locate typescript lib directory" >&2
31+ exit 1
32+fi
33+
34+cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/tags"
35+target="${cache_dir}/ts_std.tags"
36+
37+mkdir -p "${cache_dir}"
38+
39+echo "Generating TypeScript standard library tags from ${std_dir}..."
40+ctags -f "${target}" "${std_dir}"/lib.*.d.ts
41+echo "Saved tags to ${target} ($(du -h "${target}" | cut -f1))"
+37, -2
......@@ -118,11 +118,23 @@ define-command delete-buffer-picker %{
118118 # fi
119119 # }
120120
121-set-option global ctagsfiles 'tags'
121+set-option global ctagsfiles 'tags' '.tags_deps'
122122 map global user d '<a-i>w: ctags-search<ret>' -docstring 'ctags jump to definition'
123123 map global user D ': ctags-generate<ret>' -docstring 'generate tags file'
124124 map global user s ': ctags-file-symbols<ret>' -docstring 'search symbols in current file'
125125
126+define-command ctags-generate-deps -docstring "Generate .tags_deps for third-party dependencies" %{
127+ echo -markup "{Information}generating dependency tags in background..."
128+ nop %sh{ (
129+ if gen-deps-tags; then
130+ msg="dependency tags generated"
131+ else
132+ msg="failed to generate dependency tags"
133+ fi
134+ printf %s\\n "evaluate-commands -client $kak_client echo -markup '{Information}${msg}'" | kak -p ${kak_session}
135+ ) >/dev/null 2>&1 </dev/null & }
136+}
137+
126138 define-command ctags-file-symbols -docstring "Browse and jump to functions in current file" %{
127139 require-module menu
128140 evaluate-commands %sh{
......@@ -143,6 +155,29 @@ hook global BufWritePost .* %{
143155 hook global WinSetOption filetype=zig %{
144156 evaluate-commands %sh{
145157 cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
146- printf 'set-option window ctagsfiles "tags" "%s/tags/zig_std.tags"\n' "$cache_dir"
158+ printf 'set-option window ctagsfiles "tags" ".tags_deps" "%s/tags/zig_std.tags"\n' "$cache_dir"
147159 }
148160 }
161+
162+hook global WinSetOption filetype=go %{
163+ evaluate-commands %sh{
164+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
165+ printf 'set-option window ctagsfiles "tags" ".tags_deps" "%s/tags/go_std.tags"\n' "$cache_dir"
166+ }
167+}
168+
169+hook global WinSetOption filetype=python %{
170+ evaluate-commands %sh{
171+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
172+ printf 'set-option window ctagsfiles "tags" ".tags_deps" "%s/tags/python_std.tags"\n' "$cache_dir"
173+ }
174+}
175+
176+hook global WinSetOption filetype=(javascript|typescript) %{
177+ evaluate-commands %sh{
178+ cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
179+ printf 'set-option window ctagsfiles "tags" ".tags_deps" "%s/tags/ts_std.tags"\n' "$cache_dir"
180+ }
181+}
182+
183+
+0, -2
......@@ -13,7 +13,6 @@ TRACKED_FILES=(
1313 dot/npmrc
1414 dot/editorconfig
1515 dot_config/vim/vimrc
16- dot/bashrc
1716 dot_ctags.d/
1817 )
1918
......@@ -30,7 +29,6 @@ TRACKED_LOCATIONS=(
3029 ~/.npmrc
3130 ~/.editorconfig
3231 ~/.vim/vimrc
33- ~/.bashrc
3432 ~/.ctags.d/
3533 )
3634