Commit 92fc4b6
Eric Bower
·
2026-09-08 21:52:30 -0400 EDT
parent 643b832
feat: ctag lang support
7 files changed,
+313,
-31
| ... | ... | @@ -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 |
| ... | ... | @@ -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 @@ | |
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | 2 | file="$1" | |
| 3 | 3 | ||
| 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 | |
| 19 | 39 | } | |
| 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 " & # !" | |
| 24 | 40 | } | |
| 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 | + | } | |
| 31 | 71 | } | |
| 32 | - | } | |
| 33 | - | ' "$file" | |
| 72 | + | ' | |
| 73 | + | fi |
| ... | ... | @@ -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))" |
| ... | ... | @@ -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 %{ | |
| 118 | 118 | # fi | |
| 119 | 119 | # } | |
| 120 | 120 | ||
| 121 | - | set-option global ctagsfiles 'tags' | |
| 121 | + | set-option global ctagsfiles 'tags' '.tags_deps' | |
| 122 | 122 | map global user d '<a-i>w: ctags-search<ret>' -docstring 'ctags jump to definition' | |
| 123 | 123 | map global user D ': ctags-generate<ret>' -docstring 'generate tags file' | |
| 124 | 124 | map global user s ': ctags-file-symbols<ret>' -docstring 'search symbols in current file' | |
| 125 | 125 | ||
| 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 | + | ||
| 126 | 138 | define-command ctags-file-symbols -docstring "Browse and jump to functions in current file" %{ | |
| 127 | 139 | require-module menu | |
| 128 | 140 | evaluate-commands %sh{ |
| ... | ... | @@ -143,6 +155,29 @@ hook global BufWritePost .* %{ | |
| 143 | 155 | hook global WinSetOption filetype=zig %{ | |
| 144 | 156 | evaluate-commands %sh{ | |
| 145 | 157 | 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" | |
| 147 | 159 | } | |
| 148 | 160 | } | |
| 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 | + |
M
link.sh
+0,
-2