godoc: move bulk of the code to the package
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/11424043
diff --git a/godoc/linkify.go b/godoc/linkify.go
new file mode 100644
index 0000000..ea6cbcf
--- /dev/null
+++ b/godoc/linkify.go
@@ -0,0 +1,234 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file implements LinkifyText which introduces
+// links for identifiers pointing to their declarations.
+// The approach does not cover all cases because godoc
+// doesn't have complete type information, but it's
+// reasonably good for browsing.
+
+package godoc
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "io"
+ "strconv"
+)
+
+// LinkifyText HTML-escapes source text and writes it to w.
+// Identifiers that are in a "use" position (i.e., that are
+// not being declared), are wrapped with HTML links pointing
+// to the respective declaration, if possible. Comments are
+// formatted the same way as with FormatText.
+//
+func LinkifyText(w io.Writer, text []byte, n ast.Node) {
+ links := linksFor(n)
+
+ i := 0 // links index
+ prev := "" // prev HTML tag
+ linkWriter := func(w io.Writer, _ int, start bool) {
+ // end tag
+ if !start {
+ if prev != "" {
+ fmt.Fprintf(w, `</%s>`, prev)
+ prev = ""
+ }
+ return
+ }
+
+ // start tag
+ prev = ""
+ if i < len(links) {
+ switch info := links[i]; {
+ case info.path != "" && info.name == "":
+ // package path
+ fmt.Fprintf(w, `<a href="/pkg/%s/">`, info.path)
+ prev = "a"
+ case info.path != "" && info.name != "":
+ // qualified identifier
+ fmt.Fprintf(w, `<a href="/pkg/%s/#%s">`, info.path, info.name)
+ prev = "a"
+ case info.path == "" && info.name != "":
+ // local identifier
+ if info.mode == identVal {
+ fmt.Fprintf(w, `<span id="%s">`, info.name)
+ prev = "span"
+ } else if ast.IsExported(info.name) {
+ fmt.Fprintf(w, `<a href="#%s">`, info.name)
+ prev = "a"
+ }
+ }
+ i++
+ }
+ }
+
+ idents := tokenSelection(text, token.IDENT)
+ comments := tokenSelection(text, token.COMMENT)
+ FormatSelections(w, text, linkWriter, idents, selectionTag, comments)
+}
+
+// A link describes the (HTML) link information for an identifier.
+// The zero value of a link represents "no link".
+//
+type link struct {
+ mode identMode
+ path, name string // package path, identifier name
+}
+
+// linksFor returns the list of links for the identifiers used
+// by node in the same order as they appear in the source.
+//
+func linksFor(node ast.Node) (list []link) {
+ modes := identModesFor(node)
+
+ // NOTE: We are expecting ast.Inspect to call the
+ // callback function in source text order.
+ ast.Inspect(node, func(node ast.Node) bool {
+ switch n := node.(type) {
+ case *ast.Ident:
+ m := modes[n]
+ info := link{mode: m}
+ switch m {
+ case identUse:
+ if n.Obj == nil && predeclared[n.Name] {
+ info.path = BuiltinPkgPath
+ }
+ info.name = n.Name
+ case identDef:
+ // any declaration expect const or var - empty link
+ case identVal:
+ // const or var declaration
+ info.name = n.Name
+ }
+ list = append(list, info)
+ return false
+ case *ast.SelectorExpr:
+ // Detect qualified identifiers of the form pkg.ident.
+ // If anything fails we return true and collect individual
+ // identifiers instead.
+ if x, _ := n.X.(*ast.Ident); x != nil {
+ // x must be a package for a qualified identifier
+ if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
+ if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
+ // spec.Path.Value is the import path
+ if path, err := strconv.Unquote(spec.Path.Value); err == nil {
+ // Register two links, one for the package
+ // and one for the qualified identifier.
+ info := link{path: path}
+ list = append(list, info)
+ info.name = n.Sel.Name
+ list = append(list, info)
+ return false
+ }
+ }
+ }
+ }
+ }
+ return true
+ })
+
+ return
+}
+
+// The identMode describes how an identifier is "used" at its source location.
+type identMode int
+
+const (
+ identUse identMode = iota // identifier is used (must be zero value for identMode)
+ identDef // identifier is defined
+ identVal // identifier is defined in a const or var declaration
+)
+
+// identModesFor returns a map providing the identMode for each identifier used by node.
+func identModesFor(node ast.Node) map[*ast.Ident]identMode {
+ m := make(map[*ast.Ident]identMode)
+
+ ast.Inspect(node, func(node ast.Node) bool {
+ switch n := node.(type) {
+ case *ast.Field:
+ for _, n := range n.Names {
+ m[n] = identDef
+ }
+ case *ast.ImportSpec:
+ if name := n.Name; name != nil {
+ m[name] = identDef
+ }
+ case *ast.ValueSpec:
+ for _, n := range n.Names {
+ m[n] = identVal
+ }
+ case *ast.TypeSpec:
+ m[n.Name] = identDef
+ case *ast.FuncDecl:
+ m[n.Name] = identDef
+ case *ast.AssignStmt:
+ // Short variable declarations only show up if we apply
+ // this code to all source code (as opposed to exported
+ // declarations only).
+ if n.Tok == token.DEFINE {
+ // Some of the lhs variables may be re-declared,
+ // so technically they are not defs. We don't
+ // care for now.
+ for _, x := range n.Lhs {
+ // Each lhs expression should be an
+ // ident, but we are conservative and check.
+ if n, _ := x.(*ast.Ident); n != nil {
+ m[n] = identVal
+ }
+ }
+ }
+ }
+ return true
+ })
+
+ return m
+}
+
+// The predeclared map represents the set of all predeclared identifiers.
+// TODO(gri) This information is also encoded in similar maps in go/doc,
+// but not exported. Consider exporting an accessor and using
+// it instead.
+var predeclared = map[string]bool{
+ "bool": true,
+ "byte": true,
+ "complex64": true,
+ "complex128": true,
+ "error": true,
+ "float32": true,
+ "float64": true,
+ "int": true,
+ "int8": true,
+ "int16": true,
+ "int32": true,
+ "int64": true,
+ "rune": true,
+ "string": true,
+ "uint": true,
+ "uint8": true,
+ "uint16": true,
+ "uint32": true,
+ "uint64": true,
+ "uintptr": true,
+ "true": true,
+ "false": true,
+ "iota": true,
+ "nil": true,
+ "append": true,
+ "cap": true,
+ "close": true,
+ "complex": true,
+ "copy": true,
+ "delete": true,
+ "imag": true,
+ "len": true,
+ "make": true,
+ "new": true,
+ "panic": true,
+ "print": true,
+ "println": true,
+ "real": true,
+ "recover": true,
+}