Commit 5e93e06

Eric Bower  ·  2026-10-13 14:19:32 -0400 EDT
parent d1a84a9
fix: ctags
3 files changed,  +109, -0
+14, -0
......@@ -11,6 +11,16 @@ generated=0
1111 # Start from a clean slate so repeated runs don't accumulate duplicate tags.
1212 rm -f "${target}"
1313
14+# ctags records *relative* input paths with a leading "../" (a Universal Ctags
15+# quirk) that editors can't resolve. Feed it absolute paths instead.
16+absolutize_arr() {
17+ local -n _arr="$1"
18+ local i
19+ for i in "${!_arr[@]}"; do
20+ [[ "${_arr[$i]}" = /* ]] || _arr[i]="$(pwd)/${_arr[i]}"
21+ done
22+}
23+
1424 # 1. Go modules
1525 if [[ -f "go.mod" ]] && command -v go >/dev/null 2>&1; then
1626 echo "Resolving Go dependencies..."
......@@ -42,12 +52,14 @@ if [[ -f "build.zig.zon" ]] || [[ -d "zig-pkg" ]] || [[ -f ".generated-sources"
4252 # .zig-cache and are not reachable from zig-pkg, so we index them directly.
4353 if [[ -f ".generated-sources" ]]; then
4454 while IFS= read -r gf; do
55+ gf="${gf#./}"
4556 [[ -n "$gf" && -f "$gf" ]] && zig_inputs+=("$gf")
4657 done < .generated-sources
4758 fi
4859
4960 if [[ ${#zig_inputs[@]} -gt 0 ]]; then
5061 readarray -t zig_inputs < <(printf "%s\n" "${zig_inputs[@]}" | sort -u)
62+ absolutize_arr zig_inputs
5163 echo "Indexing ${#zig_inputs[@]} Zig sources into ${target}..."
5264 # NOTE: --languages=Zig (not +Zig) so we don't also index CSS/HTML/JSON/etc.
5365 tmp_tags="$(mktemp)"
......@@ -87,6 +99,7 @@ if [[ -n "${venv_dir}" ]]; then
8799 done
88100
89101 if [[ ${#sp_dirs[@]} -gt 0 ]]; then
102+ absolutize_arr sp_dirs
90103 echo "Indexing Python site-packages into ${target}..."
91104 flags=("-f" "${target}" "--languages=Python" "--exclude=*.dist-info" "--exclude=*.egg-info" "--exclude=*/tests/*" "--exclude=*/test/*")
92105 [[ $generated -eq 1 ]] && flags+=("-a")
......@@ -107,6 +120,7 @@ if [[ -f "package.json" && -d "node_modules" ]]; then
107120 done < <(find node_modules -maxdepth 5 -name "*.d.ts" ! -path "*/node_modules/@types/*" 2>/dev/null)
108121
109122 if [[ ${#ts_targets[@]} -gt 0 ]]; then
123+ absolutize_arr ts_targets
110124 echo "Indexing TypeScript types into ${target}..."
111125 flags=("-f" "${target}" "--languages=TypeScript,JavaScript" "--exclude=*/test/*" "--exclude=*/tests/*")
112126 [[ $generated -eq 1 ]] && flags+=("-a")
+59, -0
......@@ -0,0 +1,59 @@
1+#!/usr/bin/env bash
2+# tag-scope: read tags (readtags -e -n format) on stdin and append a `scope:`
3+# extension field to each tag, giving the name of the enclosing declaration.
4+#
5+# ctags doesn't record scope for Zig (or most languages) for methods that share
6+# a name (e.g. wayland's ~20 `setHandler`), so go-to-definition can't tell them
7+# apart. This recovers the enclosing type by brace-matching: for a tag at line
8+# N, it finds the nearest preceding line whose `{` opens the block still active
9+# at N, and extracts the declared name from that line.
10+#
11+# Each source file is read once (cached). Pseudo-tags (!...) and tags without a
12+# line number pass through unchanged. For languages/structures it can't parse,
13+# the scope is empty and the tag is emitted with `scope:` (harmless).
14+set -euo pipefail
15+
16+awk '
17+# Extract a plausible declaration name from a line.
18+function extract(line, s) {
19+ if (match(line, /pub[ \t]+(const|var)?[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) {
20+ s = substr(line, RSTART, RLENGTH); sub(/pub[ \t]+(const|var)?[ \t]+/, "", s); return s
21+ }
22+ if (match(line, /fn[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) {
23+ s = substr(line, RSTART, RLENGTH); sub(/fn[ \t]+/, "", s); return s
24+ }
25+ if (match(line, /(struct|class|union|type|enum)[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) {
26+ s = substr(line, RSTART, RLENGTH); sub(/^(struct|class|union|type|enum)[ \t]+/, "", s); return s
27+ }
28+ return ""
29+}
30+# Pass pseudo-tags through untouched.
31+$1 ~ /^!/ { print; next }
32+{
33+ ln = ""
34+ for (f = 1; f <= NF; f++) if ($f ~ /^line:/) ln = substr($f, 6)
35+ if (ln == "") { print; next }
36+
37+ file = $2; N = ln + 0
38+ if (!(file in loaded)) {
39+ cum = 0
40+ while ((getline l < file) > 0) {
41+ cnt[file]++
42+ o = gsub(/{/, "&", l); c = gsub(/}/, "&", l)
43+ sd[file, cnt[file]] = cum
44+ src[file, cnt[file]] = l
45+ cum += o - c
46+ }
47+ close(file)
48+ loaded[file] = 1
49+ }
50+
51+ scope = ""
52+ if (N >= 1 && N <= cnt[file]) {
53+ D = sd[file, N]
54+ for (L = N - 1; L >= 1; L--)
55+ if (sd[file, L] == D - 1 && src[file, L] ~ /\{/) { scope = extract(src[file, L]); break }
56+ }
57+ print $0 "\tscope:" scope
58+}
59+'
+36, -0
......@@ -149,6 +149,42 @@ define-command ctags-file-symbols -docstring "Browse and jump to functions in cu
149149 }
150150 }
151151
152+# Override the stock ctags-search. The stock version jumps pattern-based tags
153+# via a regex search that always lands on the FIRST match, so overloaded /
154+# duplicate names (e.g. wayland's ~20 setHandler, all identical lines) collapse
155+# to a single result. This version jumps by the exact `line:` extension field and
156+# labels each item with its enclosing type (via tag-scope) plus line number, so
157+# every overload is distinguishable and reachable, e.g.
158+# setHandler: Surface @ line 4960
159+define-command -override ctags-search -params ..1 -docstring "Jump to a symbol's definition (line-accurate, lists overloads)" %[
160+ require-module menu
161+ evaluate-commands %sh[
162+ realpath() { ( cd "$(dirname "$1")"; printf "%s/%s\n" "$(pwd -P)" "$(basename "$1")" ) }
163+ export tagname="${1:-${kak_selection}}"
164+ eval "set -- $kak_quoted_opt_ctagsfiles"
165+ for candidate in "$@"; do
166+ [ -f "$candidate" ] && realpath "$candidate"
167+ done | awk '!x[$0]++' | while read -r tags; do
168+ printf '!TAGROOT\t%s\n' "$(realpath "${tags%/*}")/"
169+ readtags -e -n -t "$tags" "$tagname"
170+ done | tag-scope | awk -F '\t' '
171+ /^!TAGROOT\t/ { tagroot = $2; next }
172+ {
173+ name = $1; path = $2
174+ line = ""; scope = ""
175+ for (i = 4; i <= NF; i++) { if ($i ~ /^line:/) line = substr($i, 6); else if ($i ~ /^scope:/) scope = substr($i, 7) }
176+ if (line == "") next
177+ menu_item = name; gsub(/!/, "!!", menu_item)
178+ menu_info = (scope != "" ? scope " @ line " : "line ") line; gsub(/!/, "!!", menu_info)
179+ edit_path = path; if (edit_path !~ /^\//) edit_path = tagroot edit_path
180+ gsub(/!/, "!!", edit_path); gsub(/#/, "##", edit_path); gsub(/&/, "&&", edit_path); gsub(/\|/, "||", edit_path)
181+ out = out "%!" menu_item ": " menu_info "! %!evaluate-commands %# try %& edit -existing %|" edit_path "|; execute-keys %|" line "gx| & catch %& fail unable to find tag & # !"
182+ }
183+ END { print (length(out) == 0 ? "fail no such tag " ENVIRON["tagname"] : "menu -auto-single " out) }
184+ '
185+ ]
186+]
187+
152188 hook global WinCreate .* %{
153189 ctags-enable-autoinfo
154190 }