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
  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
+29, -0
 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
+32, -0
 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))"
+41, -0
 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+
+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