forked from
tangled.org/core
Monorepo for Tangled
1package state
2
3import (
4 "errors"
5 "net/http"
6 "net/url"
7 "slices"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "tangled.org/core/appview/codesearch"
11 "tangled.org/core/appview/db"
12 "tangled.org/core/appview/models"
13 "tangled.org/core/appview/pages"
14 "tangled.org/core/appview/pagination"
15 "tangled.org/core/orm"
16)
17
18func (s *State) handleCodeSearch(w http.ResponseWriter, r *http.Request) {
19 l := s.logger.With("handler", "CodeSearch")
20 ctx := r.Context()
21 page := pagination.FromContext(ctx)
22 q := r.URL.Query().Get("q")
23
24 var redirected bool
25 var params pages.CodeSearchParams
26 params.BaseParams = pages.BaseParamsFromContext(ctx)
27 params.FilterQuery = q
28 params.Page = page
29 defer func() {
30 if redirected {
31 return
32 }
33 if err := s.pages.CodeSearch(w, params); err != nil {
34 l.Error("failed to render code search", "err", err)
35 }
36 }()
37
38 if q == "" {
39 return
40 }
41
42 if params.BaseParams.LoggedInUser == nil {
43 return
44 }
45
46 res, err := s.codesearch.Search(ctx, q, page)
47 if err != nil {
48 // repo-name-only queries belong to the repo search page; redirect with
49 // the rewritten query (repo: prefix dropped, lang: kept).
50 var repoErr *codesearch.RepoOnlyError
51 if errors.As(err, &repoErr) {
52 redirected = true
53 http.Redirect(w, r, "/search?q="+url.QueryEscape(repoErr.Query), http.StatusFound)
54 return
55 }
56 l.Error("code search failed", "err", err, "query", q)
57 params.ErrorMsg = "Failed to perform search. Please try again later."
58 return
59 }
60 results := res.Results
61
62 repoMap := map[syntax.DID]*models.Repo{}
63 var repoDids []string
64 for _, res := range results {
65 if res.RepoDID == "" {
66 continue
67 }
68 if _, ok := repoMap[res.RepoDID]; !ok {
69 repoMap[res.RepoDID] = nil
70 repoDids = append(repoDids, res.RepoDID.String())
71 }
72 }
73 if len(repoDids) > 0 {
74 repos, err := db.GetRepos(s.db, orm.FilterIn("repo_did", repoDids))
75 if err != nil {
76 l.Error("failed to load repos for code search", "err", err)
77 params.ErrorMsg = "Failed to load repos for code search. Please try again later."
78 return
79 }
80 for i := range repos {
81 repoMap[syntax.DID(repos[i].RepoDid)] = &repos[i]
82 }
83 }
84
85 out := make([]pages.SearchResult, 0, len(results))
86 for _, res := range results {
87 csr := pages.SearchResult{
88 RepoDID: res.RepoDID,
89 Repo: repoMap[res.RepoDID],
90 FilePath: res.FilePath,
91 Branches: res.Branches,
92 Commit: res.Commit,
93 Language: res.Language,
94 }
95 if f := res.File; f != nil {
96 csr.File = &pages.CodeSearchResult_File{
97 NameSpans: pages.FileNameSpans(res.FilePath, f.Ranges),
98 }
99 }
100 slices.SortStableFunc(res.Chunks, func(a, b models.Result_ChunkMatch) int {
101 return a.ContentStartLine - b.ContentStartLine
102 })
103 for _, c := range res.Chunks {
104 csr.Chunks = append(csr.Chunks, pages.CodeSearchResult_Chunk{
105 Lines: pages.ChunkLines(c.Content, c.ContentStartLine, c.Ranges),
106 MatchCount: len(c.Ranges),
107 })
108 }
109 out = append(out, csr)
110 }
111
112 params.Results = out
113 params.HasMore = res.HasMore
114 params.MatchCount = res.Stats.MatchCount
115 params.FileCount = res.Stats.FileCount
116 params.TimeTaken = res.Stats.Duration
117}