diff --git a/content/static/css/stylesheet.css b/content/static/css/stylesheet.css index 75fd9ffe5..ac00d0589 100644 --- a/content/static/css/stylesheet.css +++ b/content/static/css/stylesheet.css @@ -1195,6 +1195,13 @@ code { .Documentation h3 a.Documentation-source { opacity: 1; } + +.Documentation h3 a.Documentation-uses { + color: #666; + font-size: 0.8em; + opacity: 0; +} + .Documentation h2:hover a, .Documentation h3:hover a, .Documentation summary:hover a, diff --git a/internal/godoc/dochtml/dochtml.go b/internal/godoc/dochtml/dochtml.go index e2d8d5942..f1315d17e 100644 --- a/internal/godoc/dochtml/dochtml.go +++ b/internal/godoc/dochtml/dochtml.go @@ -57,6 +57,7 @@ type RenderOptions struct { // string to indicate that a given file should not be linked. FileLinkFunc func(file string) (url string) SourceLinkFunc func(ast.Node) string + UsesLinkFunc func(defParts []string) string // ModInfo optionally specifies information about the module the package // belongs to in order to render module-related documentation. ModInfo *ModuleInfo @@ -118,6 +119,9 @@ func Render(ctx context.Context, fset *token.FileSet, p *doc.Package, opt Render sourceLink := func(name string, node ast.Node) safehtml.HTML { return linkHTML(name, opt.SourceLinkFunc(node), "Documentation-source") } + usesLink := func(title string, defParts ...string) safehtml.HTML { + return sourcegraphLinkHTML("Uses", opt.UsesLinkFunc(defParts), "Documentation-uses", title) + } if experiment.IsActive(ctx, internal.ExperimentUnitPage) { if p.Doc == "" && @@ -139,6 +143,7 @@ func Render(ctx context.Context, fset *token.FileSet, p *doc.Package, opt Render "render_code": r.CodeHTML, "file_link": fileLink, "source_link": sourceLink, + "uses_link": usesLink, }) data := struct { RootURL string @@ -180,6 +185,15 @@ func linkHTML(name, url, class string) safehtml.HTML { return render.ExecuteToHTML(render.LinkTemplate, render.Link{Class: class, Href: url, Text: name}) } +// sourcegraphLinkHTML returns an HTML-formatted name linked to the given Sourcegraph URL. +// A title is needed to generate the tooltip to distinguish between different components of the code. +func sourcegraphLinkHTML(name, url, class, title string) safehtml.HTML { + if url == "" { + return safehtml.HTMLEscaped(name) + } + return render.ExecuteToHTML(render.SourcegraphLinkTemplate, render.SourcegraphLink{Class: class, Href: url, Text: name, Title: title}) +} + // examples is an internal representation of all package examples. type examples struct { List []*example // sorted by ParentID diff --git a/internal/godoc/dochtml/dochtml_test.go b/internal/godoc/dochtml/dochtml_test.go index c46537127..173ef05fc 100644 --- a/internal/godoc/dochtml/dochtml_test.go +++ b/internal/godoc/dochtml/dochtml_test.go @@ -28,6 +28,7 @@ func TestRender(t *testing.T) { rawDoc, err := Render(context.Background(), fset, d, RenderOptions{ FileLinkFunc: func(string) string { return "file" }, SourceLinkFunc: func(ast.Node) string { return "src" }, + UsesLinkFunc: func([]string) string { return "uses" }, }) if err != nil { t.Fatal(err) diff --git a/internal/godoc/dochtml/internal/render/idents.go b/internal/godoc/dochtml/internal/render/idents.go index b7911f1b3..e52cd91e8 100644 --- a/internal/godoc/dochtml/internal/render/idents.go +++ b/internal/godoc/dochtml/internal/render/idents.go @@ -318,6 +318,14 @@ type Link struct { var LinkTemplate = template.Must(template.New("link").Parse( `{{.Text}}`)) +type SourcegraphLink struct { + Href, Text, Class string + Title string // title for tooltip when the user's cursor hovers +} + +var SourcegraphLinkTemplate = template.Must(template.New("sourcegraph_link").Parse( + `{{.Text}}`)) + // lookup looks up a dot-separated identifier. // E.g., "pkg", "pkg.Var", "Recv.Method", "Struct.Field", "pkg.Struct.Field" func (r identifierResolver) lookup(id string) (pkgPath, name string, ok bool) { diff --git a/internal/godoc/dochtml/legacy_body.go b/internal/godoc/dochtml/legacy_body.go index 4d5a9e90e..9bea060b3 100644 --- a/internal/godoc/dochtml/legacy_body.go +++ b/internal/godoc/dochtml/legacy_body.go @@ -90,7 +90,7 @@ const legacyTmplBody = ` {{- range .Funcs -}}
{{- $id := safe_id .Name -}} -

func {{source_link .Name .Decl}}

{{"\n"}} +

func {{source_link .Name .Decl}} {{uses_link "List Function Callers" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -107,7 +107,7 @@ const legacyTmplBody = `
{{- $tname := .Name -}} {{- $id := safe_id .Name -}} -

type {{source_link .Name .Decl}}

{{"\n"}} +

type {{source_link .Name .Decl}} {{uses_link "List Uses of This Type" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -135,7 +135,7 @@ const legacyTmplBody = ` {{- range .Funcs -}}
{{- $id := safe_id .Name -}} -

func {{source_link .Name .Decl}}

{{"\n"}} +

func {{source_link .Name .Decl}} {{uses_link "List Function Callers" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -148,7 +148,7 @@ const legacyTmplBody = `
{{- $name := (printf "%s.%s" $tname .Name) -}} {{- $id := (safe_id $name) -}} -

func ({{.Recv}}) {{source_link .Name .Decl}}

{{"\n"}} +

func ({{.Recv}}) {{source_link .Name .Decl}} {{uses_link "List Method Callers" .Recv .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} diff --git a/internal/godoc/dochtml/legacy_template_test.go b/internal/godoc/dochtml/legacy_template_test.go index 62443d2eb..312724fd0 100644 --- a/internal/godoc/dochtml/legacy_template_test.go +++ b/internal/godoc/dochtml/legacy_template_test.go @@ -254,7 +254,7 @@ const fullTemplate = `{{- "" -}} {{- range .Funcs -}}
{{- $id := safe_id .Name -}} -

func {{source_link .Name .Decl}}

{{"\n"}} +

func {{source_link .Name .Decl}} {{uses_link "List Function Callers" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -271,7 +271,7 @@ const fullTemplate = `{{- "" -}}
{{- $tname := .Name -}} {{- $id := safe_id .Name -}} -

type {{source_link .Name .Decl}}

{{"\n"}} +

type {{source_link .Name .Decl}} {{uses_link "List Uses of This Type" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -299,7 +299,7 @@ const fullTemplate = `{{- "" -}} {{- range .Funcs -}}
{{- $id := safe_id .Name -}} -

func {{source_link .Name .Decl}}

{{"\n"}} +

func {{source_link .Name .Decl}} {{uses_link "List Function Callers" .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} @@ -312,7 +312,7 @@ const fullTemplate = `{{- "" -}}
{{- $name := (printf "%s.%s" $tname .Name) -}} {{- $id := (safe_id $name) -}} -

func ({{.Recv}}) {{source_link .Name .Decl}}

{{"\n"}} +

func ({{.Recv}}) {{source_link .Name .Decl}} {{uses_link "List Method Callers" .Recv .Name}}

{{"\n"}} {{- $out := render_decl .Doc .Decl -}} {{- $out.Decl -}} {{- $out.Doc -}} diff --git a/internal/godoc/dochtml/template.go b/internal/godoc/dochtml/template.go index 800caa914..2b2387594 100644 --- a/internal/godoc/dochtml/template.go +++ b/internal/godoc/dochtml/template.go @@ -49,6 +49,7 @@ var tmpl = map[string]interface{}{ "render_code": (*render.Renderer)(nil).CodeHTML, "file_link": func() string { return "" }, "source_link": func() string { return "" }, + "uses_link": func() string { return "" }, "play_url": func(*doc.Example) string { return "" }, "safe_id": render.SafeGoID, } diff --git a/internal/godoc/render.go b/internal/godoc/render.go index 75f59c667..d042b8187 100644 --- a/internal/godoc/render.go +++ b/internal/godoc/render.go @@ -119,10 +119,17 @@ func (p *Package) Render(ctx context.Context, innerPath string, sourceInfo *sour } return sourceInfo.FileURL(path.Join(innerPath, filename)) } + usesLinkFunc := func(defParts []string) string { + if sourceInfo == nil { + return "" + } + return sourceInfo.UsesURL(modInfo.ModulePath, importPath, defParts) + } docHTML, err := dochtml.Render(ctx, p.Fset, d, dochtml.RenderOptions{ FileLinkFunc: fileLinkFunc, SourceLinkFunc: sourceLinkFunc, + UsesLinkFunc: usesLinkFunc, ModInfo: modInfo, Limit: int64(MaxDocumentationHTML), }) diff --git a/internal/source/source.go b/internal/source/source.go index c1981ec9a..629ecd934 100644 --- a/internal/source/source.go +++ b/internal/source/source.go @@ -25,6 +25,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "path" "regexp" "strconv" @@ -112,6 +113,37 @@ func (i *Info) LineURL(pathname string, line int) string { }) } +// UsesURL returns a URL redirecting to Sourcegraph site showing the usage for a particular component of the code. +func (i *Info) UsesURL(modulePath string, importPath string, defParts []string) string { + sourcegraphBaseURL := "https://sourcegraph.com/-/godoc/refs?" + + var def string + switch len(defParts) { + case 1: + def = defParts[0] + + case 2: + typeName, methodName := defParts[0], defParts[1] + typeName = strings.TrimPrefix(typeName, "*") + def = typeName + "/" + methodName + + default: + panic(fmt.Errorf("%v defParts, want 1 or 2", len(defParts))) + } + + repo := strings.TrimPrefix(modulePath, "https://") + pkg := strings.TrimPrefix(importPath, "https://") + + q := url.Values{ + "repo": []string{repo}, + "pkg": []string{pkg}, + "def": []string{def}, + "source": []string{"pkgsite"}, + } + + return sourcegraphBaseURL + q.Encode() +} + // RawURL returns a URL referring to the raw contents of a file relative to the // module's home directory. func (i *Info) RawURL(pathname string) string {