// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package sourcegraph import ( "encoding/json" "fmt" ) // GraphQL queries for Sourcegraph API const hoverQuery = ` query Hover($repo: String!, $rev: String!, $path: String!, $line: Int!, $char: Int!) { repository(name: $repo) { commit(rev: $rev) { blob(path: $path) { lsif { hover(line: $line, character: $char) { markdown { text } range { start { line character } end { line character } } } } } } } } ` const definitionQuery = ` query Definition($repo: String!, $rev: String!, $path: String!, $line: Int!, $char: Int!) { repository(name: $repo) { commit(rev: $rev) { blob(path: $path) { lsif { definitions(line: $line, character: $char) { nodes { resource { path repository { name } commit { oid } } range { start { line character } end { line character } } } } } } } } } ` const referencesQuery = ` query References($repo: String!, $rev: String!, $path: String!, $line: Int!, $char: Int!) { repository(name: $repo) { commit(rev: $rev) { blob(path: $path) { lsif { references(line: $line, character: $char, first: 100) { nodes { resource { path repository { name } commit { oid } } range { start { line character } end { line character } } } } } } } } } ` const syncRepoMutation = ` mutation SyncRepo($repo: String!) { scheduleRepositoryPermissionsSync(repository: $repo) { alwaysNil } } ` // Response parsing structures type hoverResponse struct { Repository *struct { Commit *struct { Blob *struct { LSIF *struct { Hover *struct { Markdown *struct { Text string `json:"text"` } `json:"markdown"` Range *struct { Start struct { Line int `json:"line"` Character int `json:"character"` } `json:"start"` End struct { Line int `json:"line"` Character int `json:"character"` } `json:"end"` } `json:"range"` } `json:"hover"` } `json:"lsif"` } `json:"blob"` } `json:"commit"` } `json:"repository"` } type locationsResponse struct { Repository *struct { Commit *struct { Blob *struct { LSIF *struct { Definitions *locationNodes `json:"definitions,omitempty"` References *locationNodes `json:"references,omitempty"` } `json:"lsif"` } `json:"blob"` } `json:"commit"` } `json:"repository"` } type locationNodes struct { Nodes []struct { Resource struct { Path string `json:"path"` Repository struct { Name string `json:"name"` } `json:"repository"` Commit struct { OID string `json:"oid"` } `json:"commit"` } `json:"resource"` Range struct { Start struct { Line int `json:"line"` Character int `json:"character"` } `json:"start"` End struct { Line int `json:"line"` Character int `json:"character"` } `json:"end"` } `json:"range"` } `json:"nodes"` } func parseHoverResponse(data json.RawMessage) (*HoverResult, error) { var resp hoverResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("failed to parse hover response: %w", err) } if resp.Repository == nil || resp.Repository.Commit == nil || resp.Repository.Commit.Blob == nil || resp.Repository.Commit.Blob.LSIF == nil || resp.Repository.Commit.Blob.LSIF.Hover == nil { return nil, nil // No hover info available } hover := resp.Repository.Commit.Blob.LSIF.Hover result := &HoverResult{} if hover.Markdown != nil { result.Contents = hover.Markdown.Text } if hover.Range != nil { result.Range = &Range{ Start: Position{ Line: hover.Range.Start.Line, Character: hover.Range.Start.Character, }, End: Position{ Line: hover.Range.End.Line, Character: hover.Range.End.Character, }, } } return result, nil } func parseLocationsResponse(data json.RawMessage, field string) ([]Location, error) { var resp locationsResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("failed to parse locations response: %w", err) } if resp.Repository == nil || resp.Repository.Commit == nil || resp.Repository.Commit.Blob == nil || resp.Repository.Commit.Blob.LSIF == nil { return nil, nil } var nodes *locationNodes switch field { case "definitions": nodes = resp.Repository.Commit.Blob.LSIF.Definitions case "references": nodes = resp.Repository.Commit.Blob.LSIF.References } if nodes == nil { return nil, nil } locations := make([]Location, 0, len(nodes.Nodes)) for _, node := range nodes.Nodes { locations = append(locations, Location{ Repo: node.Resource.Repository.Name, Path: node.Resource.Path, Line: node.Range.Start.Line, Character: node.Range.Start.Character, }) } return locations, nil }