From ab01fd0abefa95c737caf64a19c71ce39125342b Mon Sep 17 00:00:00 2001 From: ajet Date: Tue, 3 Feb 2026 20:48:20 -1000 Subject: [PATCH] fix integration --- routers/api/v1/repo/sourcegraph.go | 22 +++++++-- services/sourcegraph/client.go | 9 ++++ web_src/css/features/sourcegraph.css | 6 ++- web_src/js/features/sourcegraph.ts | 71 ++++++++++++++++++++-------- 4 files changed, 84 insertions(+), 24 deletions(-) diff --git a/routers/api/v1/repo/sourcegraph.go b/routers/api/v1/repo/sourcegraph.go index db5275b852..ac374eaf4c 100644 --- a/routers/api/v1/repo/sourcegraph.go +++ b/routers/api/v1/repo/sourcegraph.go @@ -6,11 +6,19 @@ package repo import ( "net/http" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/context" sourcegraph_service "code.gitea.io/gitea/services/sourcegraph" ) +// sourcegraphRepoName returns the full repository name for Sourcegraph, +// which includes the server domain prefix (e.g., "git.example.com/owner/repo") +func sourcegraphRepoName(ctx *context.APIContext) string { + return setting.Domain + "/" + ctx.Repo.Repository.FullName() +} + // SourcegraphHover returns hover information at a position func SourcegraphHover(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/sourcegraph/hover repository repoSourcegraphHover @@ -80,7 +88,7 @@ func SourcegraphHover(ctx *context.APIContext) { return } - result, err := client.Hover(ctx, ctx.Repo.Repository.FullName(), ref, path, line, char) + result, err := client.Hover(ctx, sourcegraphRepoName(ctx), ref, path, line, char) if err != nil { ctx.APIError(http.StatusBadGateway, err) return @@ -91,6 +99,14 @@ func SourcegraphHover(ctx *context.APIContext) { return } + // Render markdown content to HTML + if result.Contents != "" { + rendered, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), result.Contents) + if err == nil { + result.Contents = string(rendered) + } + } + ctx.JSON(http.StatusOK, result) } @@ -163,7 +179,7 @@ func SourcegraphDefinition(ctx *context.APIContext) { return } - result, err := client.Definition(ctx, ctx.Repo.Repository.FullName(), ref, path, line, char) + result, err := client.Definition(ctx, sourcegraphRepoName(ctx), ref, path, line, char) if err != nil { ctx.APIError(http.StatusBadGateway, err) return @@ -245,7 +261,7 @@ func SourcegraphReferences(ctx *context.APIContext) { return } - result, err := client.References(ctx, ctx.Repo.Repository.FullName(), ref, path, line, char) + result, err := client.References(ctx, sourcegraphRepoName(ctx), ref, path, line, char) if err != nil { ctx.APIError(http.StatusBadGateway, err) return diff --git a/services/sourcegraph/client.go b/services/sourcegraph/client.go index d6711c388c..3c3e86b526 100644 --- a/services/sourcegraph/client.go +++ b/services/sourcegraph/client.go @@ -88,6 +88,8 @@ func (c *Client) graphqlRequest(ctx context.Context, query string, variables map return nil, fmt.Errorf("failed to marshal request: %w", err) } + log.Debug("Sourcegraph GraphQL request to %s: variables=%v", c.baseURL+"/.api/graphql", variables) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/.api/graphql", bytes.NewReader(bodyBytes)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) @@ -110,9 +112,12 @@ func (c *Client) graphqlRequest(ctx context.Context, query string, variables map } if resp.StatusCode != http.StatusOK { + log.Warn("Sourcegraph GraphQL error status %d: %s", resp.StatusCode, string(respBody)) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody)) } + log.Debug("Sourcegraph GraphQL response: %s", string(respBody)) + var result struct { Data json.RawMessage `json:"data"` Errors []struct { @@ -124,6 +129,7 @@ func (c *Client) graphqlRequest(ctx context.Context, query string, variables map } if len(result.Errors) > 0 { + log.Warn("Sourcegraph GraphQL errors: %v", result.Errors) return nil, fmt.Errorf("graphql error: %s", result.Errors[0].Message) } @@ -139,10 +145,13 @@ func cacheKey(queryType, repo, rev, path string, line, char int) string { func (c *Client) Hover(ctx context.Context, repo, rev, path string, line, char int) (*HoverResult, error) { key := cacheKey("hover", repo, rev, path, line, char) + log.Debug("Sourcegraph Hover request: repo=%s rev=%s path=%s line=%d char=%d", repo, rev, path, line, char) + // Try cache first if cached, hit := cache.GetCache().Get(key); hit { var result HoverResult if err := json.Unmarshal([]byte(cached), &result); err == nil { + log.Debug("Sourcegraph Hover cache hit") return &result, nil } } diff --git a/web_src/css/features/sourcegraph.css b/web_src/css/features/sourcegraph.css index ff2cc5a059..bb59d6df28 100644 --- a/web_src/css/features/sourcegraph.css +++ b/web_src/css/features/sourcegraph.css @@ -3,12 +3,16 @@ .sourcegraph-hover { max-width: 600px; max-height: 400px; - overflow: auto; + display: flex; + flex-direction: column; } .sourcegraph-hover .sg-content { padding: 8px; font-size: 13px; + overflow-y: auto; + flex: 1; + min-height: 0; /* Allow shrinking */ } .sourcegraph-hover .sg-content pre { diff --git a/web_src/js/features/sourcegraph.ts b/web_src/js/features/sourcegraph.ts index cd4c8226ca..71bc7da9a3 100644 --- a/web_src/js/features/sourcegraph.ts +++ b/web_src/js/features/sourcegraph.ts @@ -94,23 +94,25 @@ function hideRefsPanel(): void { } } -function createHoverContent( +async function createHoverContent( contents: string, config: SourcegraphConfig, path: string, line: number, char: number, -): HTMLElement { +): Promise { const el = document.createElement('div'); el.className = 'sourcegraph-hover'; - // Render markdown content as pre-formatted text (simple approach) - // In production, you might want to use a proper markdown renderer + // Content is pre-rendered as HTML by the backend const contentDiv = document.createElement('div'); - contentDiv.className = 'sg-content'; - contentDiv.innerHTML = `
${escapeHtml(contents)}
`; + contentDiv.className = 'sg-content markup'; + contentDiv.innerHTML = contents; el.appendChild(contentDiv); + // Pre-fetch definitions to know if button should be enabled + const definitions = await fetchDefinition(config, path, line, char); + // Action buttons const actionsDiv = document.createElement('div'); actionsDiv.className = 'sg-actions'; @@ -118,15 +120,19 @@ function createHoverContent( const goToDefBtn = document.createElement('button'); goToDefBtn.className = 'ui mini basic button'; goToDefBtn.textContent = 'Go to definition'; - goToDefBtn.addEventListener('click', async () => { - hideActiveTippy(); - const locations = await fetchDefinition(config, path, line, char); - if (locations.length === 1) { - navigateToLocation(locations[0]); - } else if (locations.length > 1) { - showLocationsPanel('Definitions', locations); - } - }); + if (definitions.length === 0) { + goToDefBtn.disabled = true; + goToDefBtn.classList.add('disabled'); + } else { + goToDefBtn.addEventListener('click', () => { + hideActiveTippy(); + if (definitions.length === 1) { + navigateToLocation(definitions[0]); + } else { + showLocationsPanel('Definitions', definitions); + } + }); + } actionsDiv.appendChild(goToDefBtn); const findRefsBtn = document.createElement('button'); @@ -151,6 +157,17 @@ function escapeHtml(text: string): string { return div.innerHTML; } +function normalizeRepoName(repo: string): string { + // Sourcegraph returns repo names like "git.example.com/owner/repo" + // We need just "owner/repo" for Gitea URLs + const parts = repo.split('/'); + if (parts.length >= 3) { + // Has domain prefix - return last two parts (owner/repo) + return parts.slice(-2).join('/'); + } + return repo; +} + function navigateToLocation(loc: Location): void { // Build URL to the location // If it's in the same repo, navigate to file @@ -159,14 +176,17 @@ function navigateToLocation(loc: Location): void { const repoMatch = currentPath.match(/^\/([^/]+\/[^/]+)/); const currentRepo = repoMatch ? repoMatch[1] : ''; + // Normalize repo name (strip domain prefix if present) + const targetRepo = normalizeRepoName(loc.repo || ''); + let url: string; - if (loc.repo === currentRepo || !loc.repo) { + if (targetRepo === currentRepo || !targetRepo) { // Same repo - construct relative URL const basePath = currentPath.replace(/\/src\/.*$/, ''); url = `${basePath}/src/branch/main/${loc.path}#L${loc.line + 1}`; } else { // Different repo - url = `/${loc.repo}/src/branch/main/${loc.path}#L${loc.line + 1}`; + url = `/${targetRepo}/src/branch/main/${loc.path}#L${loc.line + 1}`; } window.location.href = url; @@ -298,7 +318,7 @@ export function initSourcegraph(): void { hideActiveTippy(); // Create and show new tippy - const content = createHoverContent(hover.contents, config, pos.path, pos.line, pos.char); + const content = await createHoverContent(hover.contents, config, pos.path, pos.line, pos.char); activeTippy = createTippy(target, { content, theme: 'default', @@ -324,11 +344,22 @@ export function initSourcegraph(): void { } }); - // Close refs panel when clicking outside + // Close tippy and refs panel when clicking outside document.addEventListener('click', (e) => { - if (refsPanel && !refsPanel.contains(e.target as Node)) { + const target = e.target as Node; + + // Close refs panel if clicking outside it + if (refsPanel && !refsPanel.contains(target)) { hideRefsPanel(); } + + // Close tippy if clicking outside it + if (activeTippy) { + const tippyBox = document.querySelector('.tippy-box'); + if (tippyBox && !tippyBox.contains(target)) { + hideActiveTippy(); + } + } }); }); }