func sortReferences(d *xpb.DecorationsReply) { refs := d.Reference spans := make([]struct{ start, end int }, len(refs)) nodes := xrefs.NodesMap(d.Node) for i, r := range refs { if n, ok := nodes[r.SourceTicket]; ok { // Ignore errors; 0 works fine for sorting purposes start, _ := strconv.Atoi(string(n[schema.AnchorStartFact])) end, _ := strconv.Atoi(string(n[schema.AnchorEndFact])) spans[i] = struct{ start, end int }{start, end} } } sort.Sort(bySpan{refs, spans}) }
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) } nodes := xrefs.NodesMap(decor.Nodes) emitted := stringset.New() for _, r := range decor.Reference { if r.Kind != schema.DefinesBindingEdge || emitted.Contains(r.TargetTicket) { continue } ident := string(nodes[r.TargetTicket][identifierFact]) if ident == "" { continue } offset, err := strconv.Atoi(string(nodes[r.SourceTicket][schema.AnchorStartFact])) 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) } } }
func displayReferences(decor *xpb.DecorationsReply) error { if *displayJSON { return json.NewEncoder(out).Encode(decor) } nodes := xrefs.NodesMap(decor.Node) // TODO(schroederc): return anchor locations from server norm := xrefs.NewNormalizer(decor.SourceText) for _, ref := range decor.Reference { nodeKind := factValue(nodes, ref.TargetTicket, schema.NodeKindFact, "UNKNOWN") subkind := factValue(nodes, ref.TargetTicket, schema.SubkindFact, "") startOffset := factValue(nodes, ref.SourceTicket, schema.AnchorStartFact, "_") endOffset := factValue(nodes, ref.SourceTicket, schema.AnchorEndFact, "_") // ignore errors (locations will be 0) s, _ := strconv.Atoi(startOffset) e, _ := strconv.Atoi(endOffset) loc, err := norm.Location(&xpb.Location{ Kind: xpb.Location_SPAN, Start: &xpb.Location_Point{ByteOffset: int32(s)}, End: &xpb.Location_Point{ByteOffset: int32(e)}, }) if err != nil { return fmt.Errorf("error normalizing reference location for anchor %q: %v", ref.SourceTicket, err) } r := strings.NewReplacer( "@source@", ref.SourceTicket, "@target@", ref.TargetTicket, "@edgeKind@", ref.Kind, "@nodeKind@", nodeKind, "@subkind@", subkind, "@^offset@", startOffset, "@^line@", strconv.Itoa(int(loc.Start.LineNumber)), "@^col@", strconv.Itoa(int(loc.Start.ColumnOffset)), "@$offset@", endOffset, "@$line@", strconv.Itoa(int(loc.End.LineNumber)), "@$col@", strconv.Itoa(int(loc.End.ColumnOffset)), ) if _, err := r.WriteString(out, refFormat+"\n"); err != nil { return err } } return nil }
func getTagFields(xs xrefs.Service, ticket string) ([]string, error) { reply, err := xs.Edges(ctx, &xpb.EdgesRequest{ Ticket: []string{ticket}, Kind: []string{schema.ChildOfEdge, schema.ParamEdge}, Filter: []string{schema.NodeKindFact, schema.SubkindFact, identifierFact}, }) if err != nil || len(reply.EdgeSet) == 0 { return nil, err } var fields []string nodes := xrefs.NodesMap(reply.Node) edges := xrefs.EdgesMap(reply.EdgeSet) switch string(nodes[ticket][schema.NodeKindFact]) + "|" + string(nodes[ticket][schema.SubkindFact]) { case schema.FunctionKind + "|": fields = append(fields, "f") fields = append(fields, "arity:"+strconv.Itoa(len(edges[ticket][schema.ParamEdge]))) case schema.EnumKind + "|" + schema.EnumClassSubkind: fields = append(fields, "g") case schema.PackageKind + "|": fields = append(fields, "p") case schema.RecordKind + "|" + schema.ClassSubkind: fields = append(fields, "c") case schema.VariableKind + "|": fields = append(fields, "v") } for _, parent := range edges[ticket][schema.ChildOfEdge] { parentIdent := string(nodes[parent][identifierFact]) if parentIdent == "" { continue } switch string(nodes[parent][schema.NodeKindFact]) + "|" + string(nodes[parent][schema.SubkindFact]) { case schema.FunctionKind + "|": fields = append(fields, "function:"+parentIdent) case schema.RecordKind + "|" + schema.ClassSubkind: fields = append(fields, "class:"+parentIdent) case schema.EnumKind + "|" + schema.EnumClassSubkind: fields = append(fields, "enum:"+parentIdent) } } return fields, nil }
func getTagFields(xs xrefs.Service, ticket string) ([]string, error) { reply, err := xs.Edges(ctx, &gpb.EdgesRequest{ Ticket: []string{ticket}, Kind: []string{edges.ChildOf, edges.Param}, Filter: []string{facts.NodeKind, facts.Subkind, identifierFact}, }) if err != nil || len(reply.EdgeSets) == 0 { return nil, err } var fields []string nmap := xrefs.NodesMap(reply.Nodes) emap := xrefs.EdgesMap(reply.EdgeSets) switch string(nmap[ticket][facts.NodeKind]) + "|" + string(nmap[ticket][facts.Subkind]) { case nodes.Function + "|": fields = append(fields, "f") fields = append(fields, "arity:"+strconv.Itoa(len(emap[ticket][edges.Param]))) case nodes.Enum + "|" + nodes.EnumClass: fields = append(fields, "g") case nodes.Package + "|": fields = append(fields, "p") case nodes.Record + "|" + nodes.Class: fields = append(fields, "c") case nodes.Variable + "|": fields = append(fields, "v") } for parent := range emap[ticket][edges.ChildOf] { parentIdent := string(nmap[parent][identifierFact]) if parentIdent == "" { continue } switch string(nmap[parent][facts.NodeKind]) + "|" + string(nmap[parent][facts.Subkind]) { case nodes.Function + "|": fields = append(fields, "function:"+parentIdent) case nodes.Record + "|" + nodes.Class: fields = append(fields, "class:"+parentIdent) case nodes.Enum + "|" + nodes.EnumClass: fields = append(fields, "enum:"+parentIdent) } } return fields, nil }
func displayDecorations(decor *xpb.DecorationsReply) error { if *displayJSON { return jsonMarshaler.Marshal(out, decor) } nodes := xrefs.NodesMap(decor.Nodes) for _, ref := range decor.Reference { nodeKind := factValue(nodes, ref.TargetTicket, facts.NodeKind, "UNKNOWN") subkind := factValue(nodes, ref.TargetTicket, facts.Subkind, "") loc := &xpb.Location{ Kind: xpb.Location_SPAN, Start: ref.AnchorStart, End: ref.AnchorEnd, } var targetDef string if ref.TargetDefinition != "" { targetDef = ref.TargetDefinition // TODO(schroederc): fields from decor.DefinitionLocations // TODO(zarko): fields from decor.ExtendsOverrides } r := strings.NewReplacer( "@source@", ref.SourceTicket, "@target@", ref.TargetTicket, "@edgeKind@", ref.Kind, "@nodeKind@", nodeKind, "@subkind@", subkind, "@^offset@", itoa(loc.Start.ByteOffset), "@^line@", itoa(loc.Start.LineNumber), "@^col@", itoa(loc.Start.ColumnOffset), "@$offset@", itoa(loc.End.ByteOffset), "@$line@", itoa(loc.End.LineNumber), "@$col@", itoa(loc.End.ColumnOffset), "@targetDef@", targetDef, ) if _, err := r.WriteString(out, refFormat+"\n"); err != nil { return err } } return nil }
func displayDecorations(decor *xpb.DecorationsReply) error { if *displayJSON { return jsonMarshaler.Marshal(out, decor) } nodes := xrefs.NodesMap(decor.Node) for _, ref := range decor.Reference { nodeKind := factValue(nodes, ref.TargetTicket, schema.NodeKindFact, "UNKNOWN") subkind := factValue(nodes, ref.TargetTicket, schema.SubkindFact, "") loc := &xpb.Location{ Kind: xpb.Location_SPAN, Start: ref.AnchorStart, End: ref.AnchorEnd, } r := strings.NewReplacer( "@source@", ref.SourceTicket, "@target@", ref.TargetTicket, "@edgeKind@", ref.Kind, "@nodeKind@", nodeKind, "@subkind@", subkind, "@^offset@", itoa(loc.Start.ByteOffset), "@^line@", itoa(loc.Start.LineNumber), "@^col@", itoa(loc.Start.ColumnOffset), "@$offset@", itoa(loc.End.ByteOffset), "@$line@", itoa(loc.End.LineNumber), "@$col@", itoa(loc.End.ColumnOffset), ) if _, err := r.WriteString(out, refFormat+"\n"); err != nil { return err } } return nil }
func completeDefinition(definesAnchor string) (*definition, error) { parentReply, err := xs.Edges(ctx, &xpb.EdgesRequest{ Ticket: []string{definesAnchor}, Kind: []string{schema.ChildOfEdge}, Filter: []string{schema.NodeKindFact, schema.AnchorLocFilter}, }) if err != nil { return nil, err } parentNodes := xrefs.NodesMap(parentReply.Node) var files []string for _, parent := range xrefs.EdgesMap(parentReply.EdgeSet)[definesAnchor][schema.ChildOfEdge] { if string(parentNodes[parent][schema.NodeKindFact]) == schema.FileKind { files = append(files, parent) } } if len(files) == 0 { return nil, nil } else if len(files) > 1 { return nil, fmt.Errorf("anchor has multiple file parents %q: %v", definesAnchor, files) } vName, err := kytheuri.Parse(files[0]) if err != nil { return nil, err } start, end := parseAnchorSpan(parentNodes[definesAnchor]) return &definition{ File: vName.VName(), Start: start, End: end, }, nil }
func main() { flag.Parse() if *offset < 0 { flagutil.UsageError("non-negative --offset required") } else if *signature == "" && *path == "" { flagutil.UsageError("must provide at least --path or --signature") } if strings.HasPrefix(*remoteAPI, "http://") || strings.HasPrefix(*remoteAPI, "https://") { xs = xrefs.WebClient(*remoteAPI) idx = search.WebClient(*remoteAPI) } else { conn, err := grpc.Dial(*remoteAPI) if err != nil { log.Fatalf("Error connecting to remote API %q: %v", *remoteAPI, err) } defer conn.Close() xs = xrefs.GRPC(xpb.NewXRefServiceClient(conn)) idx = search.GRPC(spb.NewSearchServiceClient(conn)) } relPath := *path if !*ignoreLocalRepo { if _, err := os.Stat(relPath); err == nil { absPath, err := filepath.Abs(relPath) if err != nil { log.Fatal(err) } kytheRoot := findKytheRoot(filepath.Dir(absPath)) if kytheRoot != "" { relPath, err = filepath.Rel(filepath.Join(kytheRoot, *root), absPath) if err != nil { log.Fatal(err) } } } } partialFile := &spb.VName{ Signature: *signature, Corpus: *corpus, Root: *root, Path: relPath, Language: *language, } reply, err := idx.Search(ctx, &spb.SearchRequest{ Partial: partialFile, Fact: fileFacts, }) if err != nil { log.Fatalf("Error locating file {%v}: %v", partialFile, err) } if len(reply.Ticket) == 0 { log.Fatalf("Could not locate file {%v}", partialFile) } else if len(reply.Ticket) > 1 { log.Fatalf("Ambiguous file {%v}; multiple results: %v", partialFile, reply.Ticket) } fileTicket := reply.Ticket[0] decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{ // TODO(schroederc): limit Location to a SPAN around *offset Location: &xpb.Location{Ticket: fileTicket}, References: true, SourceText: true, DirtyBuffer: readDirtyBuffer(ctx), }) if err != nil { log.Fatal(err) } nodes := xrefs.NodesMap(decor.Node) en := json.NewEncoder(os.Stdout) for _, ref := range decor.Reference { start, end := parseAnchorSpan(nodes[ref.SourceTicket]) if start <= *offset && *offset < end { var r reference r.Span.Start = start r.Span.End = end r.Span.Text = string(decor.SourceText[start:end]) r.Kind = strings.TrimPrefix(ref.Kind, schema.EdgePrefix) r.Node.Ticket = ref.TargetTicket node := nodes[ref.TargetTicket] r.Node.Kind = string(node[schema.NodeKindFact]) r.Node.Subkind = string(node[schema.SubkindFact]) if eReply, err := xs.Edges(ctx, &xpb.EdgesRequest{ Ticket: []string{ref.TargetTicket}, Kind: []string{schema.NamedEdge, definedAtEdge}, }); err != nil { log.Printf("WARNING: error getting edges for %q: %v", ref.TargetTicket, err) } else { edges := xrefs.EdgesMap(eReply.EdgeSet)[ref.TargetTicket] for _, name := range edges[schema.NamedEdge] { if uri, err := kytheuri.Parse(name); err != nil { log.Printf("WARNING: named node ticket (%q) could not be parsed: %v", name, err) } else { r.Node.Names = append(r.Node.Names, uri.Signature) } } if !*skipDefinitions { for _, defAnchor := range edges[definedAtEdge] { def, err := completeDefinition(defAnchor) if err != nil { log.Printf("WARNING: failed to complete definition for %q: %v", defAnchor, err) } else { r.Node.Definitions = append(r.Node.Definitions, def) } } } } if err := en.Encode(r); err != nil { log.Fatal(err) } } } }
func main() { flag.Parse() if len(flag.Args()) == 0 { flagutil.UsageError("not given any files") } xs := xrefs.WebClient(*remoteAPI) idx := search.WebClient(*remoteAPI) for _, file := range flag.Args() { results, err := idx.Search(ctx, &spb.SearchRequest{ Partial: &spb.VName{Path: file}, Fact: []*spb.SearchRequest_Fact{{ Name: schema.NodeKindFact, Value: []byte(schema.FileKind), }}, }) if err != nil { log.Fatalf("Error searching for ticket of file %q", file) } else if len(results.Ticket) == 0 { log.Printf("Could not find ticket for file %q", file) continue } else if len(results.Ticket) != 1 { log.Printf("Multiple tickets found for file %q; choosing first from %v", file, results.Ticket) } ticket := results.Ticket[0] 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) } nodes := xrefs.NodesMap(decor.Node) emitted := stringset.New() for _, r := range decor.Reference { if r.Kind != schema.DefinesBindingEdge || emitted.Contains(r.TargetTicket) { continue } ident := string(nodes[r.TargetTicket][identifierFact]) if ident == "" { continue } offset, err := strconv.Atoi(string(nodes[r.SourceTicket][schema.AnchorStartFact])) 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) } } }
func completeAnchors(ctx context.Context, xs xrefs.NodesEdgesService, retrieveText bool, files map[string]*fileNode, edgeKind string, anchors []string) ([]*xpb.CrossReferencesReply_RelatedAnchor, error) { edgeKind = schema.Canonicalize(edgeKind) // AllEdges is relatively safe because each anchor will have very few parents (almost always 1) reply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{ Ticket: anchors, Kind: []string{schema.ChildOfEdge}, Filter: []string{ schema.NodeKindFact, schema.AnchorLocFilter, schema.SnippetLocFilter, }, }) if err != nil { return nil, err } nodes := xrefs.NodesMap(reply.Nodes) var result []*xpb.CrossReferencesReply_RelatedAnchor for ticket, es := range reply.EdgeSets { if nodeKind := string(nodes[ticket][schema.NodeKindFact]); nodeKind != schema.AnchorKind { log.Printf("Found non-anchor target to %q edge: %q (kind: %q)", edgeKind, ticket, nodeKind) continue } // Parse anchor location start/end facts so, eo, err := getSpan(nodes[ticket], schema.AnchorStartFact, schema.AnchorEndFact) if err != nil { log.Printf("Invalid anchor span for %q: %v", ticket, err) continue } // For each file parent to the anchor, add an Anchor to the result. for kind, g := range es.Groups { if kind != schema.ChildOfEdge { continue } for _, edge := range g.Edge { parent := edge.TargetTicket if parentKind := string(nodes[parent][schema.NodeKindFact]); parentKind != schema.FileKind { log.Printf("Found non-file parent to anchor: %q (kind: %q)", parent, parentKind) continue } a := &xpb.Anchor{ Ticket: ticket, Kind: edgeKind, Parent: parent, } file, ok := files[a.Parent] if !ok { nReply, err := xs.Nodes(ctx, &xpb.NodesRequest{Ticket: []string{a.Parent}}) if err != nil { return nil, fmt.Errorf("error getting file contents for %q: %v", a.Parent, err) } nMap := xrefs.NodesMap(nReply.Nodes) text := nMap[a.Parent][schema.TextFact] file = &fileNode{ text: text, encoding: string(nMap[a.Parent][schema.TextEncodingFact]), norm: xrefs.NewNormalizer(text), } files[a.Parent] = file } a.Start, a.End, err = normalizeSpan(file.norm, int32(so), int32(eo)) if err != nil { log.Printf("Invalid anchor span %q in file %q: %v", ticket, a.Parent, err) continue } if retrieveText && a.Start.ByteOffset < a.End.ByteOffset { a.Text, err = text.ToUTF8(file.encoding, file.text[a.Start.ByteOffset:a.End.ByteOffset]) if err != nil { log.Printf("Error decoding anchor text: %v", err) } } if snippetStart, snippetEnd, err := getSpan(nodes[ticket], schema.SnippetStartFact, schema.SnippetEndFact); err == nil { startPoint, endPoint, err := normalizeSpan(file.norm, int32(snippetStart), int32(snippetEnd)) if err != nil { log.Printf("Invalid snippet span %q in file %q: %v", ticket, a.Parent, err) } else { a.Snippet, err = text.ToUTF8(file.encoding, file.text[startPoint.ByteOffset:endPoint.ByteOffset]) if err != nil { log.Printf("Error decoding snippet text: %v", err) } a.SnippetStart = startPoint a.SnippetEnd = endPoint } } // fallback to a line-based snippet if the indexer did not provide its own snippet offsets if a.Snippet == "" { a.SnippetStart = &xpb.Location_Point{ ByteOffset: a.Start.ByteOffset - a.Start.ColumnOffset, LineNumber: a.Start.LineNumber, } nextLine := file.norm.Point(&xpb.Location_Point{LineNumber: a.Start.LineNumber + 1}) a.SnippetEnd = &xpb.Location_Point{ ByteOffset: nextLine.ByteOffset - 1, LineNumber: a.Start.LineNumber, ColumnOffset: a.Start.ColumnOffset + (nextLine.ByteOffset - a.Start.ByteOffset - 1), } a.Snippet, err = text.ToUTF8(file.encoding, file.text[a.SnippetStart.ByteOffset:a.SnippetEnd.ByteOffset]) if err != nil { log.Printf("Error decoding snippet text: %v", err) } } result = append(result, &xpb.CrossReferencesReply_RelatedAnchor{Anchor: a}) } break // we've handled the only /kythe/edge/childof group } } return result, 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 }
// 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 }
func displayEdgeGraph(reply *xpb.EdgesReply) error { nodes := xrefs.NodesMap(reply.Nodes) edges := make(map[string]map[string]stringset.Set) for source, es := range reply.EdgeSets { for gKind, g := range es.Groups { for _, edge := range g.Edge { tgt := edge.TargetTicket src, kind := source, gKind if schema.EdgeDirection(kind) == schema.Reverse { src, kind, tgt = tgt, schema.MirrorEdge(kind), src } groups, ok := edges[src] if !ok { groups = make(map[string]stringset.Set) edges[src] = groups } targets, ok := groups[kind] if !ok { targets = stringset.New() groups[kind] = targets } targets.Add(tgt) } } } if _, err := fmt.Println("digraph kythe {"); err != nil { return err } for ticket, node := range nodes { if _, err := fmt.Printf(` %q [label=<<table><tr><td colspan="2">%s</td></tr>`, ticket, html.EscapeString(ticket)); err != nil { return err } var facts []string for fact := range node { facts = append(facts, fact) } sort.Strings(facts) for _, fact := range facts { if _, err := fmt.Printf("<tr><td>%s</td><td>%s</td></tr>", html.EscapeString(fact), html.EscapeString(string(node[fact]))); err != nil { return err } } if _, err := fmt.Println("</table>> shape=plaintext];"); err != nil { return err } } if _, err := fmt.Println(); err != nil { return err } for src, groups := range edges { for kind, targets := range groups { for tgt := range targets { if _, err := fmt.Printf("\t%q -> %q [label=%q];\n", src, tgt, kind); err != nil { return err } } } } if _, err := fmt.Println("}"); err != nil { return err } return nil }
func main() { flag.Parse() if flag.NArg() > 0 { flagutil.UsageErrorf("unknown non-flag argument(s): %v", flag.Args()) } else if *offset < 0 && (*lineNumber < 0 || *columnOffset < 0) { flagutil.UsageError("non-negative --offset (or --line and --column) required") } else if *signature == "" && *path == "" { flagutil.UsageError("must provide at least --path or --signature") } defer (*apiFlag).Close() xs, idx = *apiFlag, *apiFlag relPath := *path if *localRepoRoot != "NONE" { if _, err := os.Stat(relPath); err == nil { absPath, err := filepath.Abs(relPath) if err != nil { log.Fatal(err) } if *dirtyBuffer == "" { *dirtyBuffer = absPath } kytheRoot := *localRepoRoot if kytheRoot == "" { kytheRoot = findKytheRoot(filepath.Dir(absPath)) } if kytheRoot != "" { relPath, err = filepath.Rel(filepath.Join(kytheRoot, *root), absPath) if err != nil { log.Fatal(err) } } } } partialFile := &spb.VName{ Signature: *signature, Corpus: *corpus, Root: *root, Path: relPath, Language: *language, } reply, err := idx.Search(ctx, &spb.SearchRequest{ Partial: partialFile, Fact: fileFacts, }) if err != nil { log.Fatalf("Error locating file {%v}: %v", partialFile, err) } if len(reply.Ticket) == 0 { log.Fatalf("Could not locate file {%v}", partialFile) } else if len(reply.Ticket) > 1 { log.Fatalf("Ambiguous file {%v}; multiple results: %v", partialFile, reply.Ticket) } fileTicket := reply.Ticket[0] text := readDirtyBuffer(ctx) decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{ // TODO(schroederc): limit Location to a SPAN around *offset Location: &xpb.Location{Ticket: fileTicket}, References: true, SourceText: true, DirtyBuffer: text, Filter: []string{ schema.NodeKindFact, schema.SubkindFact, schema.AnchorLocFilter, // TODO(schroederc): remove once backwards-compatibility fix below is removed }, }) if err != nil { log.Fatal(err) } if text == nil { text = decor.SourceText } nodes := xrefs.NodesMap(decor.Node) // Normalize point within source text point := normalizedPoint(text) en := json.NewEncoder(os.Stdout) for _, ref := range decor.Reference { var start, end int if ref.AnchorStart == nil || ref.AnchorEnd == nil { // TODO(schroederc): remove this backwards-compatibility fix start, end = parseAnchorSpan(nodes[ref.SourceTicket]) } else { start, end = int(ref.AnchorStart.ByteOffset), int(ref.AnchorEnd.ByteOffset) } if start <= point && point < end { var r reference r.Span.Start = start r.Span.End = end r.Span.Text = string(text[start:end]) r.Kind = strings.TrimPrefix(ref.Kind, schema.EdgePrefix) r.Node.Ticket = ref.TargetTicket node := nodes[ref.TargetTicket] r.Node.Kind = string(node[schema.NodeKindFact]) r.Node.Subkind = string(node[schema.SubkindFact]) if eReply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{ Ticket: []string{ref.TargetTicket}, Kind: []string{schema.NamedEdge, schema.TypedEdge, definedAtEdge, definedBindingAtEdge}, }); err != nil { log.Printf("WARNING: error getting edges for %q: %v", ref.TargetTicket, err) } else { edges := xrefs.EdgesMap(eReply.EdgeSet)[ref.TargetTicket] for _, name := range edges[schema.NamedEdge] { if uri, err := kytheuri.Parse(name); err != nil { log.Printf("WARNING: named node ticket (%q) could not be parsed: %v", name, err) } else { r.Node.Names = append(r.Node.Names, uri.Signature) } } if typed := edges[schema.TypedEdge]; len(typed) > 0 { r.Node.Typed = typed[0] } if !*skipDefinitions { defs := edges[definedAtEdge] if len(defs) == 0 { defs = edges[definedBindingAtEdge] } for _, defAnchor := range defs { def, err := completeDefinition(defAnchor) if err != nil { log.Printf("WARNING: failed to complete definition for %q: %v", defAnchor, err) } else { r.Node.Definitions = append(r.Node.Definitions, def) } } } } if err := en.Encode(r); err != nil { log.Fatal(err) } } } }
func main() { flag.Parse() if flag.NArg() > 0 { flagutil.UsageErrorf("unknown non-flag argument(s): %v", flag.Args()) } else if *offset < 0 && (*lineNumber < 0 || *columnOffset < 0) { flagutil.UsageError("non-negative --offset (or --line and --column) required") } else if *path == "" { flagutil.UsageError("must provide --path") } defer (*apiFlag).Close() xs = *apiFlag relPath := *path if *localRepoRoot != "NONE" { if _, err := os.Stat(relPath); err == nil { absPath, err := filepath.Abs(relPath) if err != nil { log.Fatal(err) } if *dirtyBuffer == "" { *dirtyBuffer = absPath } kytheRoot := *localRepoRoot if kytheRoot == "" { kytheRoot = findKytheRoot(filepath.Dir(absPath)) } if kytheRoot != "" { relPath, err = filepath.Rel(filepath.Join(kytheRoot, *root), absPath) if err != nil { log.Fatal(err) } } } } fileTicket := (&kytheuri.URI{Corpus: *corpus, Root: *root, Path: relPath}).String() point := &xpb.Location_Point{ ByteOffset: int32(*offset), LineNumber: int32(*lineNumber), ColumnOffset: int32(*columnOffset), } dirtyBuffer := readDirtyBuffer(ctx) decor, err := xs.Decorations(ctx, &xpb.DecorationsRequest{ Location: &xpb.Location{ Ticket: fileTicket, Kind: xpb.Location_SPAN, Start: point, End: point, }, SpanKind: xpb.DecorationsRequest_AROUND_SPAN, References: true, SourceText: true, DirtyBuffer: dirtyBuffer, Filter: []string{ facts.NodeKind, facts.Subkind, }, }) if err != nil { log.Fatal(err) } nodes := xrefs.NodesMap(decor.Nodes) en := json.NewEncoder(os.Stdout) for _, ref := range decor.Reference { start, end := int(ref.AnchorStart.ByteOffset), int(ref.AnchorEnd.ByteOffset) var r reference r.Span.Start = start r.Span.End = end if len(dirtyBuffer) > 0 { r.Span.Text = string(dirtyBuffer[start:end]) } // TODO(schroederc): add option to get anchor text from DecorationsReply r.Kind = strings.TrimPrefix(ref.Kind, edges.Prefix) r.Node.Ticket = ref.TargetTicket node := nodes[ref.TargetTicket] r.Node.Kind = string(node[facts.NodeKind]) r.Node.Subkind = string(node[facts.Subkind]) // TODO(schroederc): use CrossReferences method if eReply, err := xrefs.AllEdges(ctx, xs, &gpb.EdgesRequest{ Ticket: []string{ref.TargetTicket}, Kind: []string{edges.Named, edges.Typed, definedAtEdge, definedBindingAtEdge}, }); err != nil { log.Printf("WARNING: error getting edges for %q: %v", ref.TargetTicket, err) } else { matching := xrefs.EdgesMap(eReply.EdgeSets)[ref.TargetTicket] for name := range matching[edges.Named] { if uri, err := kytheuri.Parse(name); err != nil { log.Printf("WARNING: named node ticket (%q) could not be parsed: %v", name, err) } else { r.Node.Names = append(r.Node.Names, uri.Signature) } } for typed := range matching[edges.Typed] { r.Node.Typed = typed break } if !*skipDefinitions { defs := matching[definedAtEdge] if len(defs) == 0 { defs = matching[definedBindingAtEdge] } for defAnchor := range defs { def, err := completeDefinition(defAnchor) if err != nil { log.Printf("WARNING: failed to complete definition for %q: %v", defAnchor, err) } else { r.Node.Definitions = append(r.Node.Definitions, def) } } } } if err := en.Encode(r); err != nil { log.Fatal(err) } } }
func displayDecorations(text []byte, decor *xpb.DecorationsReply) error { if *displayJSON { return json.NewEncoder(out).Encode(decor) } nodes := xrefs.NodesMap(decor.Node) if text == nil { text = decor.SourceText } norm := xrefs.NewNormalizer(text) for _, ref := range decor.Reference { nodeKind := factValue(nodes, ref.TargetTicket, schema.NodeKindFact, "UNKNOWN") subkind := factValue(nodes, ref.TargetTicket, schema.SubkindFact, "") var loc *xpb.Location if ref.AnchorStart != nil && ref.AnchorEnd != nil { loc = &xpb.Location{ Kind: xpb.Location_SPAN, Start: ref.AnchorStart, End: ref.AnchorEnd, } } else { // TODO(schroederc): remove this backwards-compatibility branch startOffset := factValue(nodes, ref.SourceTicket, schema.AnchorStartFact, "_") endOffset := factValue(nodes, ref.SourceTicket, schema.AnchorEndFact, "_") // ignore errors (locations will be 0) s, _ := strconv.Atoi(startOffset) e, _ := strconv.Atoi(endOffset) var err error loc, err = norm.Location(&xpb.Location{ Kind: xpb.Location_SPAN, Start: &xpb.Location_Point{ByteOffset: int32(s)}, End: &xpb.Location_Point{ByteOffset: int32(e)}, }) if err != nil { return fmt.Errorf("error normalizing reference location for anchor %q: %v", ref.SourceTicket, err) } } r := strings.NewReplacer( "@source@", ref.SourceTicket, "@target@", ref.TargetTicket, "@edgeKind@", ref.Kind, "@nodeKind@", nodeKind, "@subkind@", subkind, "@^offset@", itoa(loc.Start.ByteOffset), "@^line@", itoa(loc.Start.LineNumber), "@^col@", itoa(loc.Start.ColumnOffset), "@$offset@", itoa(loc.End.ByteOffset), "@$line@", itoa(loc.End.LineNumber), "@$col@", itoa(loc.End.ColumnOffset), ) if _, err := r.WriteString(out, refFormat+"\n"); err != nil { return err } } return nil }