fix integration

This commit is contained in:
2026-02-03 20:48:20 -10:00
parent dfff777d04
commit ab01fd0abe
4 changed files with 84 additions and 24 deletions
+19 -3
View File
@@ -6,11 +6,19 @@ package repo
import ( import (
"net/http" "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/modules/setting"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
sourcegraph_service "code.gitea.io/gitea/services/sourcegraph" 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 // SourcegraphHover returns hover information at a position
func SourcegraphHover(ctx *context.APIContext) { func SourcegraphHover(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/sourcegraph/hover repository repoSourcegraphHover // swagger:operation GET /repos/{owner}/{repo}/sourcegraph/hover repository repoSourcegraphHover
@@ -80,7 +88,7 @@ func SourcegraphHover(ctx *context.APIContext) {
return 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 { if err != nil {
ctx.APIError(http.StatusBadGateway, err) ctx.APIError(http.StatusBadGateway, err)
return return
@@ -91,6 +99,14 @@ func SourcegraphHover(ctx *context.APIContext) {
return 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) ctx.JSON(http.StatusOK, result)
} }
@@ -163,7 +179,7 @@ func SourcegraphDefinition(ctx *context.APIContext) {
return 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 { if err != nil {
ctx.APIError(http.StatusBadGateway, err) ctx.APIError(http.StatusBadGateway, err)
return return
@@ -245,7 +261,7 @@ func SourcegraphReferences(ctx *context.APIContext) {
return 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 { if err != nil {
ctx.APIError(http.StatusBadGateway, err) ctx.APIError(http.StatusBadGateway, err)
return return
+9
View File
@@ -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) 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)) req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/.api/graphql", bytes.NewReader(bodyBytes))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err) 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 { 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)) return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(respBody))
} }
log.Debug("Sourcegraph GraphQL response: %s", string(respBody))
var result struct { var result struct {
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
Errors []struct { Errors []struct {
@@ -124,6 +129,7 @@ func (c *Client) graphqlRequest(ctx context.Context, query string, variables map
} }
if len(result.Errors) > 0 { if len(result.Errors) > 0 {
log.Warn("Sourcegraph GraphQL errors: %v", result.Errors)
return nil, fmt.Errorf("graphql error: %s", result.Errors[0].Message) 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) { func (c *Client) Hover(ctx context.Context, repo, rev, path string, line, char int) (*HoverResult, error) {
key := cacheKey("hover", repo, rev, path, line, char) 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 // Try cache first
if cached, hit := cache.GetCache().Get(key); hit { if cached, hit := cache.GetCache().Get(key); hit {
var result HoverResult var result HoverResult
if err := json.Unmarshal([]byte(cached), &result); err == nil { if err := json.Unmarshal([]byte(cached), &result); err == nil {
log.Debug("Sourcegraph Hover cache hit")
return &result, nil return &result, nil
} }
} }
+5 -1
View File
@@ -3,12 +3,16 @@
.sourcegraph-hover { .sourcegraph-hover {
max-width: 600px; max-width: 600px;
max-height: 400px; max-height: 400px;
overflow: auto; display: flex;
flex-direction: column;
} }
.sourcegraph-hover .sg-content { .sourcegraph-hover .sg-content {
padding: 8px; padding: 8px;
font-size: 13px; font-size: 13px;
overflow-y: auto;
flex: 1;
min-height: 0; /* Allow shrinking */
} }
.sourcegraph-hover .sg-content pre { .sourcegraph-hover .sg-content pre {
+51 -20
View File
@@ -94,23 +94,25 @@ function hideRefsPanel(): void {
} }
} }
function createHoverContent( async function createHoverContent(
contents: string, contents: string,
config: SourcegraphConfig, config: SourcegraphConfig,
path: string, path: string,
line: number, line: number,
char: number, char: number,
): HTMLElement { ): Promise<HTMLElement> {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'sourcegraph-hover'; el.className = 'sourcegraph-hover';
// Render markdown content as pre-formatted text (simple approach) // Content is pre-rendered as HTML by the backend
// In production, you might want to use a proper markdown renderer
const contentDiv = document.createElement('div'); const contentDiv = document.createElement('div');
contentDiv.className = 'sg-content'; contentDiv.className = 'sg-content markup';
contentDiv.innerHTML = `<pre>${escapeHtml(contents)}</pre>`; contentDiv.innerHTML = contents;
el.appendChild(contentDiv); el.appendChild(contentDiv);
// Pre-fetch definitions to know if button should be enabled
const definitions = await fetchDefinition(config, path, line, char);
// Action buttons // Action buttons
const actionsDiv = document.createElement('div'); const actionsDiv = document.createElement('div');
actionsDiv.className = 'sg-actions'; actionsDiv.className = 'sg-actions';
@@ -118,15 +120,19 @@ function createHoverContent(
const goToDefBtn = document.createElement('button'); const goToDefBtn = document.createElement('button');
goToDefBtn.className = 'ui mini basic button'; goToDefBtn.className = 'ui mini basic button';
goToDefBtn.textContent = 'Go to definition'; goToDefBtn.textContent = 'Go to definition';
goToDefBtn.addEventListener('click', async () => { if (definitions.length === 0) {
hideActiveTippy(); goToDefBtn.disabled = true;
const locations = await fetchDefinition(config, path, line, char); goToDefBtn.classList.add('disabled');
if (locations.length === 1) { } else {
navigateToLocation(locations[0]); goToDefBtn.addEventListener('click', () => {
} else if (locations.length > 1) { hideActiveTippy();
showLocationsPanel('Definitions', locations); if (definitions.length === 1) {
} navigateToLocation(definitions[0]);
}); } else {
showLocationsPanel('Definitions', definitions);
}
});
}
actionsDiv.appendChild(goToDefBtn); actionsDiv.appendChild(goToDefBtn);
const findRefsBtn = document.createElement('button'); const findRefsBtn = document.createElement('button');
@@ -151,6 +157,17 @@ function escapeHtml(text: string): string {
return div.innerHTML; 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 { function navigateToLocation(loc: Location): void {
// Build URL to the location // Build URL to the location
// If it's in the same repo, navigate to file // If it's in the same repo, navigate to file
@@ -159,14 +176,17 @@ function navigateToLocation(loc: Location): void {
const repoMatch = currentPath.match(/^\/([^/]+\/[^/]+)/); const repoMatch = currentPath.match(/^\/([^/]+\/[^/]+)/);
const currentRepo = repoMatch ? repoMatch[1] : ''; const currentRepo = repoMatch ? repoMatch[1] : '';
// Normalize repo name (strip domain prefix if present)
const targetRepo = normalizeRepoName(loc.repo || '');
let url: string; let url: string;
if (loc.repo === currentRepo || !loc.repo) { if (targetRepo === currentRepo || !targetRepo) {
// Same repo - construct relative URL // Same repo - construct relative URL
const basePath = currentPath.replace(/\/src\/.*$/, ''); const basePath = currentPath.replace(/\/src\/.*$/, '');
url = `${basePath}/src/branch/main/${loc.path}#L${loc.line + 1}`; url = `${basePath}/src/branch/main/${loc.path}#L${loc.line + 1}`;
} else { } else {
// Different repo // 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; window.location.href = url;
@@ -298,7 +318,7 @@ export function initSourcegraph(): void {
hideActiveTippy(); hideActiveTippy();
// Create and show new tippy // 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, { activeTippy = createTippy(target, {
content, content,
theme: 'default', 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) => { 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(); hideRefsPanel();
} }
// Close tippy if clicking outside it
if (activeTippy) {
const tippyBox = document.querySelector('.tippy-box');
if (tippyBox && !tippyBox.contains(target)) {
hideActiveTippy();
}
}
}); });
}); });
} }