Skip to content

Commit 3c620fe

Browse files
committed
wip
wip
1 parent d70b29c commit 3c620fe

4 files changed

Lines changed: 244 additions & 5 deletions

File tree

api/handlers_analysis.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,14 @@ func (h *Handler) handleDefinition(w http.ResponseWriter, r *http.Request) {
112112
slog.Debug("definition rg fallback result", "word", word, "hits", len(h), "elapsed", time.Since(t0))
113113
}
114114
} else {
115-
h, e = search.FindDefinitions(r.Context(), word, dir, glob)
116-
slog.Debug("definition rg result", "word", word, "engine", eng, "hits", len(h), "elapsed", time.Since(t0))
115+
currentFile := q.Get("file")
116+
if currentFile != "" {
117+
h, e = search.FindDefinitionsSmart(r.Context(), word, currentFile, hroot, glob)
118+
slog.Debug("definition rg result", "word", word, "engine", "rg-smart", "hits", len(h), "elapsed", time.Since(t0))
119+
} else {
120+
h, e = search.FindDefinitions(r.Context(), word, dir, glob)
121+
slog.Debug("definition rg result", "word", word, "engine", eng, "hits", len(h), "elapsed", time.Since(t0))
122+
}
117123
}
118124
usedEngine = eng
119125
if e == nil {

search/definition.go

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"os"
8+
"os/exec"
79
"path/filepath"
810
"regexp"
911
"strings"
@@ -24,6 +26,227 @@ func FindDefinitions(ctx context.Context, word, dir, glob string) ([]DefHit, err
2426
return FindDefinitionsN(ctx, word, dir, glob, 50)
2527
}
2628

29+
// FindDefinitionsSmart はヒューリスティック探索で定義を高速に見つける。
30+
//
31+
// 全探索を同時起動し、近い順に優先して最初のヒットを返す。
32+
//
33+
// level 0: インクルードチェーン + 対応 .c ファイル(意味的な近さ・最優先)
34+
// level 1: currentFile と同じディレクトリ
35+
// level 2: 親ディレクトリ
36+
// ...
37+
// level N: root 全体(フォールバック)
38+
//
39+
// キャッシュは呼び出し元(handleDefinition)が管理する。
40+
func FindDefinitionsSmart(ctx context.Context, word, currentFile, root, glob string) ([]DefHit, error) {
41+
if word == "" || root == "" {
42+
return nil, nil
43+
}
44+
t0 := time.Now()
45+
46+
// walk ディレクトリを近い順にリストアップ
47+
var walkDirs []string
48+
if currentFile != "" {
49+
dir := filepath.Dir(currentFile)
50+
for {
51+
rel, err := filepath.Rel(root, dir)
52+
if err != nil || strings.HasPrefix(rel, "..") {
53+
break
54+
}
55+
walkDirs = append(walkDirs, dir)
56+
if dir == root {
57+
break
58+
}
59+
dir = filepath.Dir(dir)
60+
}
61+
}
62+
if len(walkDirs) == 0 {
63+
walkDirs = []string{root}
64+
}
65+
66+
// level 0: Phase0、level 1〜N: walkDirs
67+
total := 1 + len(walkDirs)
68+
69+
type levelResult struct {
70+
level int
71+
hits []DefHit
72+
err error
73+
}
74+
75+
innerCtx, cancel := context.WithCancel(ctx)
76+
defer cancel()
77+
78+
ch := make(chan levelResult, total)
79+
80+
// level 0: インクルードチェーン + 対応 .c ファイル
81+
go func() {
82+
var hits []DefHit
83+
var err error
84+
if currentFile != "" {
85+
incs, _ := GetFileIncludes(currentFile, root)
86+
files := make([]string, 0, len(incs)+1)
87+
files = append(files, currentFile)
88+
var hFiles []string
89+
for _, inc := range incs {
90+
if inc.ID != "" {
91+
abs := filepath.Join(root, inc.ID)
92+
files = append(files, abs)
93+
hFiles = append(hFiles, abs)
94+
}
95+
}
96+
cFiles := findSiblingCFiles(innerCtx, root, hFiles)
97+
files = append(files, cFiles...)
98+
slog.Debug("FindDefinitionsSmart phase0 files", "headers", len(hFiles), "c_siblings", len(cFiles))
99+
if len(files) > 0 {
100+
hits, err = findDefinitionsInFiles(innerCtx, word, files)
101+
}
102+
}
103+
ch <- levelResult{0, hits, err}
104+
}()
105+
106+
// level 1〜N: 階層的 walk
107+
for i, d := range walkDirs {
108+
go func(level int, dir string) {
109+
var hits []DefHit
110+
var err error
111+
if dir == root {
112+
hits, err = FindDefinitionsN(innerCtx, word, root, glob, 50)
113+
} else {
114+
files := listFilesInDir(dir, glob)
115+
if len(files) > 0 {
116+
hits, err = findDefinitionsInFiles(innerCtx, word, files)
117+
}
118+
}
119+
ch <- levelResult{level, hits, err}
120+
}(i+1, d)
121+
}
122+
123+
// 結果を受け取り、近い順(level 0 最優先)にチェック
124+
received := make([]levelResult, total)
125+
done := make([]bool, total)
126+
for count := 0; count < total; count++ {
127+
r := <-ch
128+
received[r.level] = r
129+
done[r.level] = true
130+
131+
for i := 0; i < total; i++ {
132+
if !done[i] {
133+
break // 近いレベルがまだ未完
134+
}
135+
if len(received[i].hits) > 0 {
136+
cancel()
137+
slog.Debug("FindDefinitionsSmart hit", "level", i, "hits", len(received[i].hits), "elapsed", time.Since(t0))
138+
return received[i].hits, received[i].err
139+
}
140+
}
141+
}
142+
143+
return nil, nil
144+
}
145+
146+
// listFilesInDir はディレクトリ直下のファイル(サブディレクトリは含まない)を返す。
147+
// glob が指定されている場合はファイル名でフィルタする(例: "*.c,*.h")。
148+
func listFilesInDir(dir, glob string) []string {
149+
entries, err := os.ReadDir(dir)
150+
if err != nil {
151+
return nil
152+
}
153+
globs := strings.FieldsFunc(glob, func(r rune) bool { return r == ',' || r == ' ' })
154+
var files []string
155+
for _, e := range entries {
156+
if e.IsDir() {
157+
continue
158+
}
159+
name := e.Name()
160+
if len(globs) == 0 {
161+
files = append(files, filepath.Join(dir, name))
162+
continue
163+
}
164+
for _, g := range globs {
165+
if matched, _ := filepath.Match(g, name); matched {
166+
files = append(files, filepath.Join(dir, name))
167+
break
168+
}
169+
}
170+
}
171+
return files
172+
}
173+
174+
// findSiblingCFiles は .h/.hpp ファイルのリストから同名の .c ファイルを root 以下で探す。
175+
// 例: include/linux/bpf.h → kernel/bpf/bpf.c
176+
// rg --files -g bpf.c -g filter.c root を1回呼ぶだけなので高速。
177+
func findSiblingCFiles(ctx context.Context, root string, hFiles []string) []string {
178+
seen := map[string]bool{}
179+
var args []string
180+
args = append(args, "--files")
181+
for _, f := range hFiles {
182+
base := filepath.Base(f)
183+
ext := strings.ToLower(filepath.Ext(base))
184+
if ext == ".h" || ext == ".hpp" {
185+
cName := strings.TrimSuffix(base, filepath.Ext(base)) + ".c"
186+
if !seen[cName] {
187+
seen[cName] = true
188+
args = append(args, "-g", cName)
189+
}
190+
}
191+
}
192+
if len(seen) == 0 {
193+
return nil
194+
}
195+
args = append(args, root)
196+
out, err := exec.CommandContext(ctx, "rg", args...).Output()
197+
if err != nil {
198+
return nil
199+
}
200+
var result []string
201+
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
202+
if line != "" {
203+
result = append(result, line)
204+
}
205+
}
206+
return result
207+
}
208+
209+
// findDefinitionsInFiles は特定ファイルリストだけを対象に定義検索する。
210+
func findDefinitionsInFiles(ctx context.Context, word string, files []string) ([]DefHit, error) {
211+
esc := regexp.QuoteMeta(word)
212+
combined := `(?:` +
213+
`#\s*define\s+` + esc + `\b` +
214+
`|^\s*(?:typedef\s+)?(?:struct|union)\s+` + esc + `\s*(?:\{|$)` +
215+
`|^\s*(?:typedef\s+)?enum\s+` + esc + `\s*(?:\{|$)` +
216+
`|\btypedef\b.+\b` + esc + `\b\s*;` +
217+
`|^\s*\}\s*` + esc + `\s*;` +
218+
`|^\s+` + esc + `\b\s*[,=]` +
219+
`|^[^\s#/*].*\b` + esc + `\s*\(` +
220+
`)`
221+
matches, err := Search(ctx, Options{
222+
Pattern: combined,
223+
Files: files,
224+
Regex: true,
225+
CaseSensitive: true,
226+
ContextLines: -1,
227+
MaxResults: 20,
228+
})
229+
if err != nil {
230+
return nil, err
231+
}
232+
seen := map[string]bool{}
233+
var results []DefHit
234+
for _, m := range matches {
235+
key := fmt.Sprintf("%s:%d", m.File, m.Line)
236+
if seen[key] {
237+
continue
238+
}
239+
seen[key] = true
240+
results = append(results, DefHit{
241+
File: m.File,
242+
Line: m.Line,
243+
Text: strings.TrimSpace(m.Text),
244+
Kind: classifyDefKind(m.Text, word),
245+
})
246+
}
247+
return preferDefinitionHits(results), nil
248+
}
249+
27250
// FindDefinitionsN は最大件数を指定できる FindDefinitions。
28251
//
29252
// 実行戦略:

search/ripgrep.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
type Options struct {
2020
Pattern string
2121
Dir string
22+
Files []string // Dir の代わりに特定ファイルのみを対象にする
2223
CaseSensitive bool
2324
Regex bool // false = literal search
2425
WordRegexp bool // --word-regexp
@@ -81,7 +82,12 @@ func buildArgs(opts Options) []string {
8182
if opts.MaxResults > 0 {
8283
args = append(args, "--max-count", strconv.Itoa(opts.MaxResults))
8384
}
84-
args = append(args, "--", opts.Pattern, opts.Dir)
85+
args = append(args, "--", opts.Pattern)
86+
if len(opts.Files) > 0 {
87+
args = append(args, opts.Files...)
88+
} else {
89+
args = append(args, opts.Dir)
90+
}
8591
return args
8692
}
8793

static/js/editor.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,8 +1028,11 @@ function showDefPeek(hits, word, pixelPos) {
10281028
}
10291029

10301030
// ===== 定義ジャンプ =====
1031+
let _defAbortCtrl = null;
10311032
async function jumpToDefinition(word) {
10321033
if(!word || word.length < 2) return;
1034+
if(_defAbortCtrl) _defAbortCtrl.abort();
1035+
_defAbortCtrl = new AbortController();
10331036
// ジャンプ前の現在位置を履歴に記録(Alt+← で戻れるように)
10341037
const curFile = tabs[activeTabIdx]?.file;
10351038
const curLine = monacoEditor?.getPosition()?.lineNumber;
@@ -1049,14 +1052,15 @@ async function jumpToDefinition(word) {
10491052
{
10501053
const p = new URLSearchParams({word});
10511054
if (glob) p.set('glob', glob);
1055+
if (currentFile) p.set('file', currentFile);
10521056
if (typeof window.gtagsEnabled === 'function' && !window.gtagsEnabled()) p.set('gtags', '0');
10531057
try {
1054-
const r = await fetch('/api/definition?' + p);
1058+
const r = await fetch('/api/definition?' + p, {signal: _defAbortCtrl.signal});
10551059
if (r.ok) {
10561060
hits = await r.json();
10571061
totalCount = hits.length;
10581062
}
1059-
} catch {}
1063+
} catch(e) { if(e?.name === 'AbortError') { clearInterval(stimer); return; } }
10601064
}
10611065

10621066
clearInterval(stimer);

0 commit comments

Comments
 (0)