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