func readEdges(ctx context.Context, es xrefs.NodesEdgesService, files []string, edges chan<- *xpb.EdgesReply, filters []string, kinds []string) error { var eErr error for _, file := range files { if eErr == nil { reply, err := xrefs.AllEdges(ctx, es, &xpb.EdgesRequest{ Ticket: []string{file}, Filter: filters, Kind: kinds, }) if err != nil { eErr = err continue } if len(reply.EdgeSet) == 0 { // File does not have any decorations, but we still want the source text/encoding. nodeReply, err := es.Nodes(ctx, &xpb.NodesRequest{ Ticket: []string{file}, Filter: filters, }) if err != nil { return fmt.Errorf("error getting file node: %v", err) } reply.Node = nodeReply.Node } edges <- reply } } return eErr }
func getDecorations(ctx context.Context, es xrefs.EdgesService, anchor *xpb.NodeInfo) ([]*srvpb.FileDecorations_Decoration, error) { var ( isAnchor bool start, end int err error ) for _, f := range anchor.Fact { switch f.Name { case schema.NodeKindFact: if string(f.Value) == schema.AnchorKind { isAnchor = true } case schema.AnchorStartFact: start, err = strconv.Atoi(string(f.Value)) if err != nil { return nil, fmt.Errorf("invalid anchor %q start offset: %q", anchor.Ticket, string(f.Value)) } case schema.AnchorEndFact: end, err = strconv.Atoi(string(f.Value)) if err != nil { return nil, fmt.Errorf("invalid anchor %q end offset: %q", anchor.Ticket, string(f.Value)) } } } if !isAnchor { return nil, nil } else if start > end { log.Printf("Invalid anchor span %d:%d for %q", start, end, anchor.Ticket) return nil, nil } edges, err := xrefs.AllEdges(ctx, es, &xpb.EdgesRequest{Ticket: []string{anchor.Ticket}}) if err != nil { return nil, err } if len(edges.EdgeSet) != 1 { return nil, fmt.Errorf("invalid number of EdgeSets returned for anchor: %d", len(edges.EdgeSet)) } a := &srvpb.FileDecorations_Decoration_Anchor{ Ticket: anchor.Ticket, StartOffset: int32(start), EndOffset: int32(end), } var ds []*srvpb.FileDecorations_Decoration for _, grp := range edges.EdgeSet[0].Group { if schema.EdgeDirection(grp.Kind) == schema.Forward && grp.Kind != schema.ChildOfEdge { for _, target := range grp.TargetTicket { ds = append(ds, &srvpb.FileDecorations_Decoration{ Anchor: a, Kind: grp.Kind, TargetTicket: target, }) } } } return ds, nil }
func completeDefinition(defAnchor string) (*definition, error) { parentReply, err := xrefs.AllEdges(ctx, xs, &xpb.EdgesRequest{ Ticket: []string{defAnchor}, 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)[defAnchor][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", defAnchor, files) } vName, err := kytheuri.Parse(files[0]) if err != nil { return nil, err } start, end := parseAnchorSpan(parentNodes[defAnchor]) return &definition{ File: vName.VName(), Start: start, End: end, }, nil }
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 }
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) } } }