// Decorations implements part of the xrefs Service interface. func (t *tableImpl) 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]*xpb.NodeInfo) seenTarget := stringset.New() // Reference.TargetTicket -> NodeInfo (superset of reply.Nodes) var nodes map[string]*xpb.NodeInfo if len(patterns) > 0 { nodes = make(map[string]*xpb.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 } } 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 = "" } reply.Reference = append(reply.Reference, r) if !seenTarget.Contains(r.TargetTicket) && nodes != nil { reply.Nodes[r.TargetTicket] = nodes[r.TargetTicket] seenTarget.Add(r.TargetTicket) } } } } // 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) } defs, err := xrefs.SlowDefinitions(t, ctx, 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 { for _, ref := range refs[tgt] { if def.Ticket != ref.SourceTicket { ref.TargetDefinition = 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 *tableImpl) 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, fmt.Errorf("decorations not found for file %q", ticket) } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.SourceText 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.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { // Set of node tickets for which to retrieve facts. These are the nodes // used in the returned references (both anchor sources and node targets). nodeTickets := stringset.New() var patcher *xrefs.Patcher var offsetMapping map[string]span // Map from anchor ticket to patched span if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer) offsetMapping = make(map[string]span) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) 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 start >= startBoundary && end <= endBoundary { if offsetMapping != nil { // Save the patched span to update the corresponding facts of the // anchor node in reply.Node. offsetMapping[d.Anchor.Ticket] = span{start, end} } reply.Reference = append(reply.Reference, decorationToReference(norm, d)) nodeTickets.Add(d.Anchor.Ticket) nodeTickets.Add(d.TargetTicket) } } } // Only request Nodes when there are fact filters given. if len(req.Filter) > 0 { // Retrieve facts for all nodes referenced in the file decorations. nodesReply, err := t.Nodes(ctx, &xpb.NodesRequest{ Ticket: nodeTickets.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("error getting nodes: %v", err) } reply.Node = nodesReply.Node } // Patch anchor node facts in reply to match dirty buffer if len(offsetMapping) > 0 { for _, n := range reply.Node { if span, ok := offsetMapping[n.Ticket]; ok { for _, f := range n.Fact { switch f.Name { case schema.AnchorStartFact: f.Value = []byte(strconv.Itoa(int(span.start))) case schema.AnchorEndFact: f.Value = []byte(strconv.Itoa(int(span.end))) } } } } } } return reply, nil }
// Decorations implements part of the Service interface. func (g *GraphStoreService) Decorations(ctx context.Context, req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if len(req.DirtyBuffer) > 0 { return nil, errors.New("UNIMPLEMENTED: dirty buffers") } else if req.GetLocation() == nil { // TODO(schroederc): allow empty location when given dirty buffer return nil, errors.New("missing location") } fileVName, err := kytheuri.ToVName(req.Location.Ticket) if err != nil { return nil, fmt.Errorf("invalid file ticket %q: %v", req.Location.Ticket, err) } text, encoding, err := getSourceText(ctx, g.gs, fileVName) if err != nil { return nil, fmt.Errorf("failed to retrieve file text: %v", err) } norm := xrefs.NewNormalizer(text) loc, err := norm.Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{ Location: loc, Nodes: make(map[string]*xpb.NodeInfo), } // Handle DecorationsRequest.SourceText switch if req.SourceText { if loc.Kind == xpb.Location_FILE { reply.SourceText = text } else { reply.SourceText = text[loc.Start.ByteOffset:loc.End.ByteOffset] } reply.Encoding = encoding } // Handle DecorationsRequest.References switch if req.References { // Traverse the following chain of edges: // file --%/kythe/edge/childof-> []anchor --forwardEdgeKind-> []target // // Add []anchor and []target nodes to reply.Nodes // Add all {anchor, forwardEdgeKind, target} tuples to reply.Reference patterns := xrefs.ConvertFilters(req.Filter) children, err := getEdges(ctx, g.gs, fileVName, func(e *spb.Entry) bool { return e.EdgeKind == revChildOfEdgeKind }) if err != nil { return nil, fmt.Errorf("failed to retrieve file children: %v", err) } targetSet := stringset.New() for _, edge := range children { anchor := edge.Target ticket := kytheuri.ToString(anchor) anchorNodeReply, err := g.Nodes(ctx, &xpb.NodesRequest{ Ticket: []string{ticket}, }) if err != nil { return nil, fmt.Errorf("failure getting reference source node: %v", err) } else if len(anchorNodeReply.Nodes) != 1 { return nil, fmt.Errorf("found %d nodes for {%+v}", len(anchorNodeReply.Nodes), anchor) } node, ok := xrefs.NodesMap(anchorNodeReply.Nodes)[ticket] if !ok { return nil, fmt.Errorf("failed to find info for node %q", ticket) } else if string(node[schema.NodeKindFact]) != schema.AnchorKind { // Skip child if it isn't an anchor node continue } anchorStart, err := strconv.Atoi(string(node[schema.AnchorStartFact])) if err != nil { log.Printf("Invalid anchor start offset %q for node %q: %v", node[schema.AnchorStartFact], ticket, err) continue } anchorEnd, err := strconv.Atoi(string(node[schema.AnchorEndFact])) if err != nil { log.Printf("Invalid anchor end offset %q for node %q: %v", node[schema.AnchorEndFact], ticket, err) continue } if loc.Kind == xpb.Location_SPAN { // Check if anchor fits within/around requested source text window if !xrefs.InSpanBounds(req.SpanKind, int32(anchorStart), int32(anchorEnd), loc.Start.ByteOffset, loc.End.ByteOffset) { continue } else if anchorStart > anchorEnd { log.Printf("Invalid anchor offset span %d:%d", anchorStart, anchorEnd) continue } } targets, err := getEdges(ctx, g.gs, anchor, func(e *spb.Entry) bool { return schema.EdgeDirection(e.EdgeKind) == schema.Forward && e.EdgeKind != schema.ChildOfEdge }) if err != nil { return nil, fmt.Errorf("failed to retrieve targets of anchor %v: %v", anchor, err) } if len(targets) == 0 { log.Printf("Anchor missing forward edges: {%+v}", anchor) continue } if node := filterNode(patterns, anchorNodeReply.Nodes[ticket]); node != nil { reply.Nodes[ticket] = node } for _, edge := range targets { targetTicket := kytheuri.ToString(edge.Target) targetSet.Add(targetTicket) reply.Reference = append(reply.Reference, &xpb.DecorationsReply_Reference{ SourceTicket: ticket, Kind: edge.Kind, TargetTicket: targetTicket, AnchorStart: norm.ByteOffset(int32(anchorStart)), AnchorEnd: norm.ByteOffset(int32(anchorEnd)), }) } } sort.Sort(bySpan(reply.Reference)) // Only request Nodes when there are fact filters given. if len(req.Filter) > 0 { // Ensure returned nodes are not duplicated. for ticket := range reply.Nodes { targetSet.Remove(ticket) } // Batch request all Reference target nodes nodesReply, err := g.Nodes(ctx, &xpb.NodesRequest{ Ticket: targetSet.Slice(), Filter: req.Filter, }) if err != nil { return nil, fmt.Errorf("failure getting reference target nodes: %v", err) } for ticket, node := range nodesReply.Nodes { reply.Nodes[ticket] = node } } } return reply, nil }
// Decorations implements part of the xrefs Service interface. func (t *tableImpl) 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, fmt.Errorf("decorations not found for file %q", ticket) } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } text := decor.SourceText 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.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) nodeTickets := stringset.New() var patcher *xrefs.Patcher var offsetMapping map[string]span // Map from anchor ticket to patched span if len(req.DirtyBuffer) > 0 { patcher = xrefs.NewPatcher(decor.SourceText, req.DirtyBuffer) offsetMapping = make(map[string]span) } // The span with which to constrain the set of returned anchor references. var startBoundary, endBoundary int32 if loc.Kind == xpb.Location_FILE { startBoundary = 0 endBoundary = int32(len(text)) } else { startBoundary = loc.Start.ByteOffset endBoundary = loc.End.ByteOffset } reply.Reference = make([]*xpb.DecorationsReply_Reference, 0, len(decor.Decoration)) 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 start >= startBoundary && end <= endBoundary { if offsetMapping != nil { // Save the patched span to update the corresponding facts of the // anchor node in reply.Node. offsetMapping[d.Anchor.Ticket] = span{start, end} } reply.Reference = append(reply.Reference, decorationToReference(norm, d)) if len(patterns) > 0 && !nodeTickets.Contains(d.Target.Ticket) { nodeTickets.Add(d.Target.Ticket) reply.Node = append(reply.Node, nodeToInfo(patterns, d.Target)) } } } } } 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 len(req.DirtyBuffer) > 0 { log.Println("TODO: implement DecorationsRequest.DirtyBuffer") return nil, errors.New("dirty buffers unimplemented") } else if req.GetLocation() == nil { // TODO(schroederc): allow empty location when given dirty buffer return nil, errors.New("missing location") } var decor srvpb.FileDecorations ticket := req.GetLocation().Ticket if err := t.Lookup(DecorationsKey(ticket), &decor); err == table.ErrNoSuchKey { return nil, fmt.Errorf("decorations not found for file %q", ticket) } else if err != nil { return nil, fmt.Errorf("lookup error for file decorations %q: %v", ticket, err) } loc, err := xrefs.NewNormalizer(decor.SourceText).Location(req.GetLocation()) if err != nil { return nil, err } reply := &xpb.DecorationsReply{Location: loc} if req.SourceText { reply.Encoding = decor.Encoding if loc.Kind == xpb.Location_FILE { reply.SourceText = decor.SourceText } else { reply.SourceText = decor.SourceText[loc.Start.ByteOffset:loc.End.ByteOffset] } } if req.References { nodeTickets := stringset.New() if loc.Kind == xpb.Location_FILE { reply.Reference = make([]*xpb.DecorationsReply_Reference, len(decor.Decoration)) for i, d := range decor.Decoration { reply.Reference[i] = decorationToReference(d) nodeTickets.Add(d.Anchor.Ticket) nodeTickets.Add(d.TargetTicket) } } else { for _, d := range decor.Decoration { if d.Anchor.StartOffset >= loc.Start.ByteOffset && d.Anchor.EndOffset <= loc.End.ByteOffset { reply.Reference = append(reply.Reference, decorationToReference(d)) nodeTickets.Add(d.Anchor.Ticket) nodeTickets.Add(d.TargetTicket) } } } nodesReply, err := t.Nodes(ctx, &xpb.NodesRequest{Ticket: nodeTickets.Slice()}) if err != nil { return nil, fmt.Errorf("error getting nodes: %v", err) } reply.Node = nodesReply.Node } return reply, nil }
// Decorations implements part of the xrefs.Service interface. func (db *DB) Decorations(req *xpb.DecorationsRequest) (*xpb.DecorationsReply, error) { if req.GetLocation() == nil { return nil, errors.New("missing location") } else if req.Location.Kind != xpb.Location_FILE { return nil, fmt.Errorf("%s location kind unimplemented", req.Location.Kind) } else if len(req.DirtyBuffer) != 0 { return nil, errors.New("dirty buffers unimplemented") } fileTicket := req.Location.Ticket edgesReply, err := db.Edges(&xpb.EdgesRequest{ Ticket: []string{fileTicket}, Kind: []string{revChildOfEdge}, Filter: []string{schema.NodeKindFact, schema.TextFact, schema.TextEncodingFact}, }) if err != nil { return nil, err } reply := &xpb.DecorationsReply{ Location: &xpb.Location{ Ticket: fileTicket, }, } nodes := xrefs.NodesMap(edgesReply.Node) if req.SourceText { if nodes[fileTicket] == nil { nodesReply, err := db.Nodes(&xpb.NodesRequest{Ticket: []string{fileTicket}}) if err != nil { return nil, err } nodes = xrefs.NodesMap(nodesReply.Node) } reply.SourceText = nodes[fileTicket][schema.TextFact] reply.Encoding = string(nodes[fileTicket][schema.TextEncodingFact]) } nodeTickets := stringset.New() if req.References { // Traverse the following chain of edges: // file --%/kythe/edge/childof-> []anchor --forwardEdgeKind-> []target edges := xrefs.EdgesMap(edgesReply.EdgeSet) for _, anchor := range edges[fileTicket][revChildOfEdge] { if string(nodes[anchor][schema.NodeKindFact]) != schema.AnchorKind { continue } uri, err := kytheuri.Parse(anchor) if err != nil { return nil, fmt.Errorf("invalid anchor ticket found %q: %v", anchor, err) } rows, err := db.anchorEdgesStmt.Query(schema.ChildOfEdge, uri.Signature, uri.Corpus, uri.Root, uri.Path, uri.Language) if err != nil { return nil, fmt.Errorf("anchor %q edge query error: %v", anchor, err) } for rows.Next() { var target kytheuri.URI var kind string if err := rows.Scan(&target.Signature, &target.Corpus, &target.Root, &target.Path, &target.Language, &kind); err != nil { return nil, fmt.Errorf("anchor %q edge scan error: %v", anchor, err) } ticket := target.String() reply.Reference = append(reply.Reference, &xpb.DecorationsReply_Reference{ SourceTicket: anchor, Kind: kind, TargetTicket: ticket, }) nodeTickets.Add(anchor, ticket) } } } nodesReply, err := db.Nodes(&xpb.NodesRequest{Ticket: nodeTickets.Slice()}) if err != nil { return nil, err } reply.Node = nodesReply.Node return reply, nil }