func main() { flag.Parse() if len(flag.Args()) == 0 { flagutil.UsageError("not given any files") } xs := xrefs.WebClient(*remoteAPI) for _, file := range flag.Args() { ticket := (&kytheuri.URI{Corpus: *corpus, Path: file}).String() decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{ Location: &xpb.Location{Ticket: ticket}, SourceText: true, References: true, }) if err != nil { log.Fatalf("Failed to get decorations for file %q", file) } nmap := xrefs.NodesMap(decor.Nodes) var emitted stringset.Set for _, r := range decor.Reference { if r.Kind != edges.DefinesBinding || emitted.Contains(r.TargetTicket) { continue } ident := string(nmap[r.TargetTicket][identifierFact]) if ident == "" { continue } offset, err := strconv.Atoi(string(nmap[r.SourceTicket][facts.AnchorStart])) if err != nil { log.Printf("Invalid start offset for anchor %q", r.SourceTicket) continue } fields, err := getTagFields(xs, r.TargetTicket) if err != nil { log.Printf("Failed to get tagfields for %q: %v", r.TargetTicket, err) } fmt.Printf("%s\t%s\t%d;\"\t%s\n", ident, file, offsetLine(decor.SourceText, offset), strings.Join(fields, "\t")) emitted.Add(r.TargetTicket) } } }
// CrossReferences implements part of the xrefs.Service interface. func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { tickets, err := xrefs.FixTickets(req.Ticket) if err != nil { return nil, err } stats := refStats{ max: int(req.PageSize), } if stats.max < 0 { return nil, fmt.Errorf("invalid page_size: %d", req.PageSize) } else if stats.max == 0 { stats.max = defaultPageSize } else if stats.max > maxPageSize { stats.max = maxPageSize } var edgesPageToken string if req.PageToken != "" { rec, err := base64.StdEncoding.DecodeString(req.PageToken) if err != nil { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } var t ipb.PageToken if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } stats.skip = int(t.Index) edgesPageToken = t.SecondaryToken } pageToken := stats.skip reply := &xpb.CrossReferencesReply{ CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet, len(req.Ticket)), Nodes: make(map[string]*cpb.NodeInfo, len(req.Ticket)), Total: &xpb.CrossReferencesReply_Total{}, } var nextToken *ipb.PageToken wantMoreCrossRefs := edgesPageToken == "" && (req.DefinitionKind != xpb.CrossReferencesRequest_NO_DEFINITIONS || req.DeclarationKind != xpb.CrossReferencesRequest_NO_DECLARATIONS || req.ReferenceKind != xpb.CrossReferencesRequest_NO_REFERENCES || req.DocumentationKind != xpb.CrossReferencesRequest_NO_DOCUMENTATION || req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS) for _, ticket := range tickets { // TODO(schroederc): retrieve PagedCrossReferences in parallel cr, err := t.crossReferences(ctx, ticket) if err == table.ErrNoSuchKey { log.Println("Missing CrossReferences:", ticket) continue } else if err != nil { return nil, fmt.Errorf("error looking up cross-references for ticket %q: %v", ticket, err) } crs := &xpb.CrossReferencesReply_CrossReferenceSet{ Ticket: ticket, } if req.ExperimentalSignatures { crs.DisplayName, err = xrefs.SlowSignature(ctx, t, ticket) if err != nil { log.Printf("WARNING: error looking up signature for ticket %q: %v", ticket, err) } } for _, grp := range cr.Group { switch { case xrefs.IsDefKind(req.DefinitionKind, grp.Kind, cr.Incomplete): reply.Total.Definitions += int64(len(grp.Anchor)) if wantMoreCrossRefs { stats.addAnchors(&crs.Definition, grp.Anchor, req.AnchorText) } case xrefs.IsDeclKind(req.DeclarationKind, grp.Kind, cr.Incomplete): reply.Total.Declarations += int64(len(grp.Anchor)) if wantMoreCrossRefs { stats.addAnchors(&crs.Declaration, grp.Anchor, req.AnchorText) } case xrefs.IsDocKind(req.DocumentationKind, grp.Kind): reply.Total.Documentation += int64(len(grp.Anchor)) if wantMoreCrossRefs { stats.addAnchors(&crs.Documentation, grp.Anchor, req.AnchorText) } case xrefs.IsRefKind(req.ReferenceKind, grp.Kind): reply.Total.References += int64(len(grp.Anchor)) if wantMoreCrossRefs { stats.addAnchors(&crs.Reference, grp.Anchor, req.AnchorText) } } } if wantMoreCrossRefs && req.CallerKind != xpb.CrossReferencesRequest_NO_CALLERS { anchors, err := xrefs.SlowCallersForCrossReferences(ctx, t, req.CallerKind == xpb.CrossReferencesRequest_OVERRIDE_CALLERS, req.ExperimentalSignatures, ticket) if err != nil { return nil, fmt.Errorf("error in SlowCallersForCrossReferences: %v", err) } reply.Total.Callers += int64(len(anchors)) stats.addRelatedAnchors(&crs.Caller, anchors, req.AnchorText) } if wantMoreCrossRefs && req.DeclarationKind != xpb.CrossReferencesRequest_NO_DECLARATIONS { decls, err := xrefs.SlowDeclarationsForCrossReferences(ctx, t, ticket) if err != nil { return nil, fmt.Errorf("error in SlowDeclarations: %v", err) } for _, decl := range decls { if decl == ticket { continue // Added in the original loop above. } cr, err := t.crossReferences(ctx, decl) if err == table.ErrNoSuchKey { log.Println("Missing CrossReferences:", decl) continue } else if err != nil { return nil, fmt.Errorf("error looking up cross-references for ticket %q: %v", ticket, err) } for _, grp := range cr.Group { if xrefs.IsDeclKind(req.DeclarationKind, grp.Kind, cr.Incomplete) { reply.Total.Declarations += int64(len(grp.Anchor)) stats.addAnchors(&crs.Declaration, grp.Anchor, req.AnchorText) } } } } for _, idx := range cr.PageIndex { switch { case xrefs.IsDefKind(req.DefinitionKind, idx.Kind, cr.Incomplete): reply.Total.Definitions += int64(idx.Count) if wantMoreCrossRefs && !stats.skipPage(idx) { p, err := t.crossReferencesPage(ctx, idx.PageKey) if err != nil { return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey) } stats.addAnchors(&crs.Definition, p.Group.Anchor, req.AnchorText) } case xrefs.IsDeclKind(req.DeclarationKind, idx.Kind, cr.Incomplete): reply.Total.Declarations += int64(idx.Count) if wantMoreCrossRefs && !stats.skipPage(idx) { p, err := t.crossReferencesPage(ctx, idx.PageKey) if err != nil { return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey) } stats.addAnchors(&crs.Declaration, p.Group.Anchor, req.AnchorText) } case xrefs.IsDocKind(req.DocumentationKind, idx.Kind): reply.Total.Documentation += int64(idx.Count) if wantMoreCrossRefs && !stats.skipPage(idx) { p, err := t.crossReferencesPage(ctx, idx.PageKey) if err != nil { return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey) } stats.addAnchors(&crs.Documentation, p.Group.Anchor, req.AnchorText) } case xrefs.IsRefKind(req.ReferenceKind, idx.Kind): reply.Total.References += int64(idx.Count) if wantMoreCrossRefs && !stats.skipPage(idx) { p, err := t.crossReferencesPage(ctx, idx.PageKey) if err != nil { return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey) } stats.addAnchors(&crs.Reference, p.Group.Anchor, req.AnchorText) } } } if len(crs.Declaration) > 0 || len(crs.Definition) > 0 || len(crs.Reference) > 0 || len(crs.Documentation) > 0 || len(crs.Caller) > 0 { reply.CrossReferences[crs.Ticket] = crs } } if pageToken+stats.total != sumTotalCrossRefs(reply.Total) && stats.total != 0 { nextToken = &ipb.PageToken{Index: int32(pageToken + stats.total)} } if len(req.Filter) > 0 { er, err := t.edges(ctx, edgesRequest{ Tickets: tickets, Filters: req.Filter, Kinds: func(kind string) bool { return !edges.IsAnchorEdge(kind) }, PageToken: edgesPageToken, TotalOnly: (stats.max <= stats.total), PageSize: stats.max - stats.total, }) if err != nil { return nil, fmt.Errorf("error getting related nodes: %v", err) } reply.Total.RelatedNodesByRelation = er.TotalEdgesByKind for ticket, es := range er.EdgeSets { var nodes stringset.Set crs, ok := reply.CrossReferences[ticket] if !ok { crs = &xpb.CrossReferencesReply_CrossReferenceSet{ Ticket: ticket, } } for kind, g := range es.Groups { for _, edge := range g.Edge { nodes.Add(edge.TargetTicket) crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{ RelationKind: kind, Ticket: edge.TargetTicket, Ordinal: edge.Ordinal, }) } } if len(nodes) > 0 { for ticket, n := range er.Nodes { if nodes.Contains(ticket) { reply.Nodes[ticket] = n } } } if !ok && len(crs.RelatedNode) > 0 { reply.CrossReferences[ticket] = crs } } if er.NextPageToken != "" { nextToken = &ipb.PageToken{SecondaryToken: er.NextPageToken} } } if nextToken != nil { rec, err := proto.Marshal(nextToken) if err != nil { return nil, fmt.Errorf("internal error: error marshalling page token: %v", err) } reply.NextPageToken = base64.StdEncoding.EncodeToString(rec) } if req.NodeDefinitions { nodeTickets := make([]string, 0, len(reply.Nodes)) for ticket := range reply.Nodes { nodeTickets = append(nodeTickets, ticket) } // TODO(schroederc): cache this in the serving data defs, err := xrefs.SlowDefinitions(ctx, t, nodeTickets) if err != nil { return nil, fmt.Errorf("error retrieving node definitions: %v", err) } reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs)) for ticket, def := range defs { node, ok := reply.Nodes[ticket] if !ok { panic(fmt.Sprintf("extra definition returned for unknown node %q: %v", ticket, def)) } node.Definition = def.Ticket if _, ok := reply.DefinitionLocations[def.Ticket]; !ok { reply.DefinitionLocations[def.Ticket] = def } } } return reply, nil }
// Decorations implements part of the xrefs Service interface. func (t *Table) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if req.GetLocation() == nil || req.GetLocation().Ticket == "" { return nil, errors.New("missing location") } ticket, err := kytheuri.Fix(req.GetLocation().Ticket) if err != nil { return nil, fmt.Errorf("invalid ticket %q: %v", req.GetLocation().Ticket, err) } decor, err := t.fileDecorations(ctx, ticket) if err == table.ErrNoSuchKey { return nil, xrefs.ErrDecorationsNotFound } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.File.Text if len(req.DirtyBuffer) > 0 { text = req.DirtyBuffer } norm := xrefs.NewNormalizer(text) loc, err := norm.Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{Location: loc} if req.SourceText { reply.Encoding = decor.File.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { patterns := xrefs.ConvertFilters(req.Filter) var patcher *xrefs.Patcher if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.File.Text, req.DirtyBuffer) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 spanKind := req.SpanKind if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) spanKind = xpb.DecorationsRequest_WITHIN_SPAN } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) reply.Nodes = make(map[string]*cpb.NodeInfo) var seenTarget stringset.Set // Reference.TargetTicket -> NodeInfo (superset of reply.Nodes) var nodes map[string]*cpb.NodeInfo if len(patterns) > 0 { nodes = make(map[string]*cpb.NodeInfo) for _, n := range decor.Target { nodes[n.Ticket] = nodeToInfo(patterns, n) } } // Reference.TargetTicket -> []Reference set var refs map[string][]*xpb.DecorationsReply_Reference // ExpandedAnchor.Ticket -> ExpandedAnchor var defs map[string]*srvpb.ExpandedAnchor if req.TargetDefinitions { refs = make(map[string][]*xpb.DecorationsReply_Reference) reply.DefinitionLocations = make(map[string]*xpb.Anchor) defs = make(map[string]*srvpb.ExpandedAnchor) for _, def := range decor.TargetDefinitions { defs[def.Ticket] = def } } var bindings []string for _, d := range decor.Decoration { start, end, exists := patcher.Patch(d.Anchor.StartOffset, d.Anchor.EndOffset) // Filter non-existent anchor. Anchors can no longer exist if we were // given a dirty buffer and the anchor was inside a changed region. if exists { if xrefs.InSpanBounds(spanKind, start, end, startBoundary, endBoundary) { d.Anchor.StartOffset = start d.Anchor.EndOffset = end r := decorationToReference(norm, d) if req.TargetDefinitions { if def, ok := defs[d.TargetDefinition]; ok { reply.DefinitionLocations[d.TargetDefinition] = a2a(def, false).Anchor } else { refs[r.TargetTicket] = append(refs[r.TargetTicket], r) } } else { r.TargetDefinition = "" } if req.ExtendsOverrides && r.Kind == edges.DefinesBinding { bindings = append(bindings, r.TargetTicket) } reply.Reference = append(reply.Reference, r) if !seenTarget.Contains(r.TargetTicket) && nodes != nil { reply.Nodes[r.TargetTicket] = nodes[r.TargetTicket] seenTarget.Add(r.TargetTicket) } } } } var extendsOverrides map[string][]*xpb.DecorationsReply_Override var extendsOverridesTargets stringset.Set if len(bindings) != 0 { extendsOverrides, err = xrefs.SlowOverrides(ctx, t, bindings) if err != nil { return nil, fmt.Errorf("lookup error for overrides tickets: %v", err) } if len(extendsOverrides) != 0 { reply.ExtendsOverrides = make(map[string]*xpb.DecorationsReply_Overrides, len(extendsOverrides)) for ticket, eos := range extendsOverrides { // Note: extendsOverrides goes out of scope after this loop, so downstream code won't accidentally // mutate reply.ExtendsOverrides via aliasing. pb := &xpb.DecorationsReply_Overrides{Override: eos} for _, eo := range eos { extendsOverridesTargets.Add(eo.Ticket) } reply.ExtendsOverrides[ticket] = pb } } } if len(extendsOverridesTargets) != 0 && len(patterns) > 0 { // Add missing NodeInfo. request := &gpb.NodesRequest{Filter: req.Filter} for ticket := range extendsOverridesTargets { if _, ok := reply.Nodes[ticket]; !ok { request.Ticket = append(request.Ticket, ticket) } } if len(request.Ticket) > 0 { nodes, err := t.Nodes(ctx, request) if err != nil { return nil, fmt.Errorf("lookup error for overrides nodes: %v", err) } for ticket, node := range nodes.Nodes { reply.Nodes[ticket] = node } } } // Only compute target definitions if the serving data doesn't contain any // TODO(schroederc): remove this once serving data is always populated if req.TargetDefinitions && len(defs) == 0 { targetTickets := make([]string, 0, len(refs)) for ticket := range refs { targetTickets = append(targetTickets, ticket) } for ticket := range extendsOverridesTargets { targetTickets = append(targetTickets, ticket) } defs, err := xrefs.SlowDefinitions(ctx, t, targetTickets) if err != nil { return nil, fmt.Errorf("error retrieving target definitions: %v", err) } reply.DefinitionLocations = make(map[string]*xpb.Anchor, len(defs)) for tgt, def := range defs { if extendsOverridesTargets.Contains(tgt) { if _, ok := reply.DefinitionLocations[tgt]; !ok { reply.DefinitionLocations[tgt] = def } } if refsTgt, ok := refs[tgt]; ok { for _, ref := range refsTgt { if def.Ticket != ref.SourceTicket { ref.TargetDefinition = def.Ticket if _, ok := reply.DefinitionLocations[def.Ticket]; !ok { reply.DefinitionLocations[def.Ticket] = def } } } } } } } return reply, nil }
func (t *Table) edges(ctx context.Context, req edgesRequest) (*gpb.EdgesReply, error) { stats := filterStats{ max: int(req.PageSize), } if req.TotalOnly { stats.max = 0 } else if stats.max < 0 { return nil, fmt.Errorf("invalid page_size: %d", req.PageSize) } else if stats.max == 0 { stats.max = defaultPageSize } else if stats.max > maxPageSize { stats.max = maxPageSize } if req.PageToken != "" { rec, err := base64.StdEncoding.DecodeString(req.PageToken) if err != nil { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } var t ipb.PageToken if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } stats.skip = int(t.Index) } pageToken := stats.skip var nodeTickets stringset.Set rs, err := t.pagedEdgeSets(ctx, req.Tickets) if err != nil { return nil, err } defer func() { // drain channel in case of errors or early return for _ = range rs { } }() patterns := xrefs.ConvertFilters(req.Filters) reply := &gpb.EdgesReply{ EdgeSets: make(map[string]*gpb.EdgeSet), Nodes: make(map[string]*cpb.NodeInfo), TotalEdgesByKind: make(map[string]int64), } for r := range rs { if r.Err == table.ErrNoSuchKey { continue } else if r.Err != nil { return nil, r.Err } pes := r.PagedEdgeSet countEdgeKinds(pes, req.Kinds, reply.TotalEdgesByKind) // Don't scan the EdgeSet_Groups if we're already at the specified page_size. if stats.total == stats.max { continue } groups := make(map[string]*gpb.EdgeSet_Group) for _, grp := range pes.Group { if req.Kinds == nil || req.Kinds(grp.Kind) { ng, ns := stats.filter(grp) if ng != nil { for _, n := range ns { if len(patterns) > 0 && !nodeTickets.Contains(n.Ticket) { nodeTickets.Add(n.Ticket) reply.Nodes[n.Ticket] = nodeToInfo(patterns, n) } } groups[grp.Kind] = ng if stats.total == stats.max { break } } } } // TODO(schroederc): ensure that pes.EdgeSet.Groups and pes.PageIndexes of // the same kind are grouped together in the EdgesReply if stats.total != stats.max { for _, idx := range pes.PageIndex { if req.Kinds == nil || req.Kinds(idx.EdgeKind) { if stats.skipPage(idx) { log.Printf("Skipping EdgePage: %s", idx.PageKey) continue } log.Printf("Retrieving EdgePage: %s", idx.PageKey) ep, err := t.edgePage(ctx, idx.PageKey) if err == table.ErrNoSuchKey { return nil, fmt.Errorf("internal error: missing edge page: %q", idx.PageKey) } else if err != nil { return nil, fmt.Errorf("edge page lookup error (page key: %q): %v", idx.PageKey, err) } ng, ns := stats.filter(ep.EdgesGroup) if ng != nil { for _, n := range ns { if len(patterns) > 0 && !nodeTickets.Contains(n.Ticket) { nodeTickets.Add(n.Ticket) reply.Nodes[n.Ticket] = nodeToInfo(patterns, n) } } groups[ep.EdgesGroup.Kind] = ng if stats.total == stats.max { break } } } } } if len(groups) > 0 { reply.EdgeSets[pes.Source.Ticket] = &gpb.EdgeSet{Groups: groups} if len(patterns) > 0 && !nodeTickets.Contains(pes.Source.Ticket) { nodeTickets.Add(pes.Source.Ticket) reply.Nodes[pes.Source.Ticket] = nodeToInfo(patterns, pes.Source) } } } totalEdgesPossible := int(sumEdgeKinds(reply.TotalEdgesByKind)) if stats.total > stats.max { log.Panicf("totalEdges greater than maxEdges: %d > %d", stats.total, stats.max) } else if pageToken+stats.total > totalEdgesPossible && pageToken <= totalEdgesPossible { log.Panicf("pageToken+totalEdges greater than totalEdgesPossible: %d+%d > %d", pageToken, stats.total, totalEdgesPossible) } if pageToken+stats.total != totalEdgesPossible && stats.total != 0 { rec, err := proto.Marshal(&ipb.PageToken{Index: int32(pageToken + stats.total)}) if err != nil { return nil, fmt.Errorf("internal error: error marshalling page token: %v", err) } reply.NextPageToken = base64.StdEncoding.EncodeToString(rec) } return reply, nil }
// CrossReferences implements part of the xrefs.Interface. func (d *DB) CrossReferences(ctx context.Context, req *xpb.CrossReferencesRequest) (*xpb.CrossReferencesReply, error) { // TODO(zarko): Callgraph integration. tickets, err := xrefs.FixTickets(req.Ticket) if err != nil { return nil, err } pageSize := int(req.PageSize) if pageSize <= 0 { pageSize = defaultPageSize } else if pageSize > maxPageSize { pageSize = maxPageSize } var pageOffset int var edgesToken string if req.PageToken != "" { rec, err := base64.StdEncoding.DecodeString(req.PageToken) if err != nil { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } var t ipb.PageToken if err := proto.Unmarshal(rec, &t); err != nil || t.Index < 0 { return nil, fmt.Errorf("invalid page_token: %q", req.PageToken) } pageOffset = int(t.Index) edgesToken = t.SecondaryToken } reply := &xpb.CrossReferencesReply{ CrossReferences: make(map[string]*xpb.CrossReferencesReply_CrossReferenceSet), Nodes: make(map[string]*cpb.NodeInfo), } setQ, ticketArgs := sqlSetQuery(1, tickets) var count int if edgesToken == "" { args := append(ticketArgs, pageSize+1, pageOffset) // +1 to check for next page rs, err := d.Query(fmt.Sprintf("SELECT ticket, kind, proto FROM CrossReferences WHERE ticket IN %s ORDER BY ticket LIMIT $%d OFFSET $%d;", setQ, len(tickets)+1, len(tickets)+2), args...) if err != nil { return nil, err } defer closeRows(rs) var xrs *xpb.CrossReferencesReply_CrossReferenceSet for rs.Next() { count++ if count > pageSize { continue } var ticket, kind string var rec []byte if err := rs.Scan(&ticket, &kind, &rec); err != nil { return nil, err } if xrs != nil && xrs.Ticket != ticket { if len(xrs.Definition) > 0 || len(xrs.Documentation) > 0 || len(xrs.Reference) > 0 || len(xrs.RelatedNode) > 0 { reply.CrossReferences[xrs.Ticket] = xrs } xrs = nil } if xrs == nil { xrs = &xpb.CrossReferencesReply_CrossReferenceSet{Ticket: ticket} } switch { // TODO(schroederc): handle declarations case xrefs.IsDefKind(req.DefinitionKind, kind, false): xrs.Definition, err = addRelatedAnchor(xrs.Definition, rec, req.AnchorText) if err != nil { return nil, err } case xrefs.IsDocKind(req.DocumentationKind, kind): xrs.Documentation, err = addRelatedAnchor(xrs.Documentation, rec, req.AnchorText) if err != nil { return nil, err } case xrefs.IsRefKind(req.ReferenceKind, kind): xrs.Reference, err = addRelatedAnchor(xrs.Reference, rec, req.AnchorText) if err != nil { return nil, err } } } if xrs != nil && (len(xrs.Definition) > 0 || len(xrs.Documentation) > 0 || len(xrs.Reference) > 0 || len(xrs.RelatedNode) > 0) { reply.CrossReferences[xrs.Ticket] = xrs } if count > pageSize { rec, err := proto.Marshal(&ipb.PageToken{Index: int32(pageOffset + pageSize)}) if err != nil { return nil, fmt.Errorf("internal error: error marshalling page token: %v", err) } reply.NextPageToken = base64.StdEncoding.EncodeToString(rec) } } if len(req.Filter) > 0 && count <= pageSize { // TODO(schroederc): consolidate w/ LevelDB implementation er, err := d.edges(ctx, &gpb.EdgesRequest{ Ticket: tickets, Filter: req.Filter, PageSize: int32(pageSize - count), PageToken: edgesToken, }, func(kind string) bool { return !edges.IsAnchorEdge(kind) }) if err != nil { return nil, fmt.Errorf("error getting related nodes: %v", err) } for ticket, es := range er.EdgeSets { var nodes stringset.Set crs, ok := reply.CrossReferences[ticket] if !ok { crs = &xpb.CrossReferencesReply_CrossReferenceSet{ Ticket: ticket, } } for kind, g := range es.Groups { if !edges.IsAnchorEdge(kind) { for _, edge := range g.Edge { nodes.Add(edge.TargetTicket) crs.RelatedNode = append(crs.RelatedNode, &xpb.CrossReferencesReply_RelatedNode{ RelationKind: kind, Ticket: edge.TargetTicket, Ordinal: edge.Ordinal, }) } } } if len(nodes) > 0 { for ticket, n := range er.Nodes { if nodes.Contains(ticket) { reply.Nodes[ticket] = n } } } if !ok && len(crs.RelatedNode) > 0 { reply.CrossReferences[ticket] = crs } } if er.NextPageToken != "" { rec, err := proto.Marshal(&ipb.PageToken{SecondaryToken: er.NextPageToken}) if err != nil { return nil, fmt.Errorf("internal error: error marshalling page token: %v", err) } reply.NextPageToken = base64.StdEncoding.EncodeToString(rec) } } return reply, nil }