internal/godoc: factor out example, ast support funcs

Moved with

rf '
	mv \
		Presentation.example_htmlFunc \
		replaceLeadingIndentation \
		exampleOutputRx \
		filterOutBuildAnnotations \
		Presentation.example_nameFunc \
		Presentation.example_suffixFunc \
		splitExampleName \
		stripExampleSuffix \
		startsWithUppercase \
		examplefuncs.go

	mv \
		slashSlash \
		Presentation.nodeFunc \
		Presentation.node_htmlFunc \
		Presentation.writeNode \
		firstIdent \
		comment_htmlFunc \
		sanitizeFunc \
		astfuncs.go
	rm Presentation.WriteNode

'

plus deleting the doc comments and import path comments,
which didn't move well.

Change-Id: I71b1dd58527938cafccfc901a0dadcc6dff34664
Reviewed-on: https://go-review.googlesource.com/c/website/+/296375
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/godoc/astfuncs.go b/internal/godoc/astfuncs.go
new file mode 100644
index 0000000..48ab123
--- /dev/null
+++ b/internal/godoc/astfuncs.go
@@ -0,0 +1,190 @@
+// 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.
+
+//go:build go1.16
+// +build go1.16
+
+package godoc
+
+import (
+	"bufio"
+	"bytes"
+	"go/ast"
+	"go/doc"
+	"go/printer"
+	"go/token"
+	"io"
+	"log"
+	"unicode"
+
+	"golang.org/x/website/internal/texthtml"
+)
+
+var slashSlash = []byte("//")
+
+func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
+	var buf bytes.Buffer
+	p.writeNode(&buf, info, info.FSet, node)
+	return buf.String()
+}
+
+func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
+	var buf1 bytes.Buffer
+	p.writeNode(&buf1, info, info.FSet, node)
+
+	var buf2 bytes.Buffer
+	var n ast.Node
+	if linkify && p.DeclLinks {
+		n, _ = node.(ast.Node)
+	}
+	buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
+		AST:        n,
+		GoComments: true,
+	}))
+	return buf2.String()
+}
+
+// writeNode writes the AST node x to w.
+//
+// The provided fset must be non-nil. The pageInfo is optional. If
+// present, the pageInfo is used to add comments to struct fields to
+// say which version of Go introduced them.
+func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
+	// convert trailing tabs into spaces using a tconv filter
+	// to ensure a good outcome in most browsers (there may still
+	// be tabs in comments and strings, but converting those into
+	// the right number of spaces is much harder)
+	//
+	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
+	//           with an another printer mode (which is more efficiently
+	//           implemented in the printer than here with another layer)
+
+	var pkgName, structName string
+	var apiInfo pkgAPIVersions
+	if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
+		p.Corpus != nil &&
+		gd.Tok == token.TYPE && len(gd.Specs) != 0 {
+		pkgName = pageInfo.PDoc.ImportPath
+		if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
+			if _, ok := ts.Type.(*ast.StructType); ok {
+				structName = ts.Name.Name
+			}
+		}
+		apiInfo = p.Corpus.pkgAPIInfo[pkgName]
+	}
+
+	var out = w
+	var buf bytes.Buffer
+	if structName != "" {
+		out = &buf
+	}
+
+	mode := printer.TabIndent | printer.UseSpaces
+	err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(TabSpacer(out, p.TabWidth), fset, x)
+	if err != nil {
+		log.Print(err)
+	}
+
+	// Add comments to struct fields saying which Go version introduced them.
+	if structName != "" {
+		fieldSince := apiInfo.fieldSince[structName]
+		typeSince := apiInfo.typeSince[structName]
+		// Add/rewrite comments on struct fields to note which Go version added them.
+		var buf2 bytes.Buffer
+		buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
+		bs := bufio.NewScanner(&buf)
+		for bs.Scan() {
+			line := bs.Bytes()
+			field := firstIdent(line)
+			var since string
+			if field != "" {
+				since = fieldSince[field]
+				if since != "" && since == typeSince {
+					// Don't highlight field versions if they were the
+					// same as the struct itself.
+					since = ""
+				}
+			}
+			if since == "" {
+				buf2.Write(line)
+			} else {
+				if bytes.Contains(line, slashSlash) {
+					line = bytes.TrimRight(line, " \t.")
+					buf2.Write(line)
+					buf2.WriteString("; added in Go ")
+				} else {
+					buf2.Write(line)
+					buf2.WriteString(" // Go ")
+				}
+				buf2.WriteString(since)
+			}
+			buf2.WriteByte('\n')
+		}
+		w.Write(buf2.Bytes())
+	}
+}
+
+// firstIdent returns the first identifier in x.
+// This actually parses "identifiers" that begin with numbers too, but we
+// never feed it such input, so it's fine.
+func firstIdent(x []byte) string {
+	x = bytes.TrimSpace(x)
+	i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
+	if i == -1 {
+		return string(x)
+	}
+	return string(x[:i])
+}
+
+func comment_htmlFunc(comment string) string {
+	var buf bytes.Buffer
+	// TODO(gri) Provide list of words (e.g. function parameters)
+	//           to be emphasized by ToHTML.
+	doc.ToHTML(&buf, comment, nil) // does html-escaping
+	return buf.String()
+}
+
+// sanitizeFunc sanitizes the argument src by replacing newlines with
+// blanks, removing extra blanks, and by removing trailing whitespace
+// and commas before closing parentheses.
+func sanitizeFunc(src string) string {
+	buf := make([]byte, len(src))
+	j := 0      // buf index
+	comma := -1 // comma index if >= 0
+	for i := 0; i < len(src); i++ {
+		ch := src[i]
+		switch ch {
+		case '\t', '\n', ' ':
+			// ignore whitespace at the beginning, after a blank, or after opening parentheses
+			if j == 0 {
+				continue
+			}
+			if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
+				continue
+			}
+			// replace all whitespace with blanks
+			ch = ' '
+		case ',':
+			comma = j
+		case ')', '}', ']':
+			// remove any trailing comma
+			if comma >= 0 {
+				j = comma
+			}
+			// remove any trailing whitespace
+			if j > 0 && buf[j-1] == ' ' {
+				j--
+			}
+		default:
+			comma = -1
+		}
+		buf[j] = ch
+		j++
+	}
+	// remove trailing blank, if any
+	if j > 0 && buf[j-1] == ' ' {
+		j--
+	}
+	return string(buf[:j])
+}
diff --git a/internal/godoc/examplefuncs.go b/internal/godoc/examplefuncs.go
new file mode 100644
index 0000000..a1048bb
--- /dev/null
+++ b/internal/godoc/examplefuncs.go
@@ -0,0 +1,235 @@
+// 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.
+
+//go:build go1.16
+// +build go1.16
+
+package godoc
+
+import (
+	"bytes"
+	"go/ast"
+	"go/format"
+	"go/printer"
+	"log"
+	"regexp"
+	"strings"
+	"unicode"
+	"unicode/utf8"
+)
+
+func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
+	var buf bytes.Buffer
+	for _, eg := range info.Examples {
+		name := stripExampleSuffix(eg.Name)
+
+		if name != funcName {
+			continue
+		}
+
+		// print code
+		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+		code := p.node_htmlFunc(info, cnode, true)
+		out := eg.Output
+		wholeFile := true
+
+		// Additional formatting if this is a function body.
+		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
+			wholeFile = false
+			// remove surrounding braces
+			code = code[1 : n-1]
+			// unindent
+			code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
+			// remove output comment
+			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
+				code = strings.TrimSpace(code[:loc[0]])
+			}
+		}
+
+		// Write out the playground code in standard Go style
+		// (use tabs, no comment highlight, etc).
+		play := ""
+		if eg.Play != nil && p.ShowPlayground {
+			var buf bytes.Buffer
+			eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
+			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
+				log.Print(err)
+			} else {
+				play = buf.String()
+			}
+		}
+
+		// Drop output, as the output comment will appear in the code.
+		if wholeFile && play == "" {
+			out = ""
+		}
+
+		if p.ExampleHTML == nil {
+			out = ""
+			return ""
+		}
+
+		err := p.ExampleHTML.Execute(&buf, struct {
+			Name, Doc, Code, Play, Output string
+			GoogleCN                      bool
+		}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
+		if err != nil {
+			log.Print(err)
+		}
+	}
+	return buf.String()
+}
+
+// replaceLeadingIndentation replaces oldIndent at the beginning of each line
+// with newIndent. This is used for formatting examples. Raw strings that
+// span multiple lines are handled specially: oldIndent is not removed (since
+// go/printer will not add any indentation there), but newIndent is added
+// (since we may still want leading indentation).
+func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
+	// Handle indent at the beginning of the first line. After this, we handle
+	// indentation only after a newline.
+	var buf bytes.Buffer
+	if strings.HasPrefix(body, oldIndent) {
+		buf.WriteString(newIndent)
+		body = body[len(oldIndent):]
+	}
+
+	// Use a state machine to keep track of whether we're in a string or
+	// rune literal while we process the rest of the code.
+	const (
+		codeState = iota
+		runeState
+		interpretedStringState
+		rawStringState
+	)
+	searchChars := []string{
+		"'\"`\n", // codeState
+		`\'`,     // runeState
+		`\"`,     // interpretedStringState
+		"`\n",    // rawStringState
+		// newlineState does not need to search
+	}
+	state := codeState
+	for {
+		i := strings.IndexAny(body, searchChars[state])
+		if i < 0 {
+			buf.WriteString(body)
+			break
+		}
+		c := body[i]
+		buf.WriteString(body[:i+1])
+		body = body[i+1:]
+		switch state {
+		case codeState:
+			switch c {
+			case '\'':
+				state = runeState
+			case '"':
+				state = interpretedStringState
+			case '`':
+				state = rawStringState
+			case '\n':
+				if strings.HasPrefix(body, oldIndent) {
+					buf.WriteString(newIndent)
+					body = body[len(oldIndent):]
+				}
+			}
+
+		case runeState:
+			switch c {
+			case '\\':
+				r, size := utf8.DecodeRuneInString(body)
+				buf.WriteRune(r)
+				body = body[size:]
+			case '\'':
+				state = codeState
+			}
+
+		case interpretedStringState:
+			switch c {
+			case '\\':
+				r, size := utf8.DecodeRuneInString(body)
+				buf.WriteRune(r)
+				body = body[size:]
+			case '"':
+				state = codeState
+			}
+
+		case rawStringState:
+			switch c {
+			case '`':
+				state = codeState
+			case '\n':
+				buf.WriteString(newIndent)
+			}
+		}
+	}
+	return buf.String()
+}
+
+var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
+
+func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
+	if len(cg) == 0 {
+		return cg
+	}
+
+	for i := range cg {
+		if !strings.HasPrefix(cg[i].Text(), "+build ") {
+			// Found the first non-build tag, return from here until the end
+			// of the slice.
+			return cg[i:]
+		}
+	}
+
+	// There weren't any non-build tags, return an empty slice.
+	return []*ast.CommentGroup{}
+}
+
+// example_nameFunc takes an example function name and returns its display
+// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
+func (p *Presentation) example_nameFunc(s string) string {
+	name, suffix := splitExampleName(s)
+	// replace _ with . for method names
+	name = strings.Replace(name, "_", ".", 1)
+	// use "Package" if no name provided
+	if name == "" {
+		name = "Package"
+	}
+	return name + suffix
+}
+
+// example_suffixFunc takes an example function name and returns its suffix in
+// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
+func (p *Presentation) example_suffixFunc(name string) string {
+	_, suffix := splitExampleName(name)
+	return suffix
+}
+
+func splitExampleName(s string) (name, suffix string) {
+	i := strings.LastIndex(s, "_")
+	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
+		name = s[:i]
+		suffix = " (" + strings.Title(s[i+1:]) + ")"
+		return
+	}
+	name = s
+	return
+}
+
+// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
+// while keeping uppercase Braz in Foo_Braz.
+func stripExampleSuffix(name string) string {
+	if i := strings.LastIndex(name, "_"); i != -1 {
+		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
+			name = name[:i]
+		}
+	}
+	return name
+}
+
+func startsWithUppercase(s string) bool {
+	r, _ := utf8.DecodeRuneInString(s)
+	return unicode.IsUpper(r)
+}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index 47c2b0f..4b80021 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -5,34 +5,19 @@
 //go:build go1.16
 // +build go1.16
 
-// Package godoc is a work-in-progress (2013-07-17) package to
-// begin splitting up the godoc binary into multiple pieces.
-//
-// This package comment will evolve over time as this package splits
-// into smaller pieces.
-package godoc // import "golang.org/x/website/internal/godoc"
+package godoc
 
 import (
-	"bufio"
 	"bytes"
 	"fmt"
 	"go/ast"
 	"go/doc"
-	"go/format"
-	"go/printer"
 	"go/token"
-	"io"
-	"log"
 	pathpkg "path"
-	"regexp"
 	"strconv"
 	"strings"
 	"text/template"
 	"time"
-	"unicode"
-	"unicode/utf8"
-
-	"golang.org/x/website/internal/texthtml"
 )
 
 // Fake relative package path for built-ins. Documentation for all globals
@@ -118,80 +103,6 @@
 	return localname
 }
 
-func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
-	var buf bytes.Buffer
-	p.writeNode(&buf, info, info.FSet, node)
-	return buf.String()
-}
-
-func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
-	var buf1 bytes.Buffer
-	p.writeNode(&buf1, info, info.FSet, node)
-
-	var buf2 bytes.Buffer
-	var n ast.Node
-	if linkify && p.DeclLinks {
-		n, _ = node.(ast.Node)
-	}
-	buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
-		AST:        n,
-		GoComments: true,
-	}))
-	return buf2.String()
-}
-
-func comment_htmlFunc(comment string) string {
-	var buf bytes.Buffer
-	// TODO(gri) Provide list of words (e.g. function parameters)
-	//           to be emphasized by ToHTML.
-	doc.ToHTML(&buf, comment, nil) // does html-escaping
-	return buf.String()
-}
-
-// sanitizeFunc sanitizes the argument src by replacing newlines with
-// blanks, removing extra blanks, and by removing trailing whitespace
-// and commas before closing parentheses.
-func sanitizeFunc(src string) string {
-	buf := make([]byte, len(src))
-	j := 0      // buf index
-	comma := -1 // comma index if >= 0
-	for i := 0; i < len(src); i++ {
-		ch := src[i]
-		switch ch {
-		case '\t', '\n', ' ':
-			// ignore whitespace at the beginning, after a blank, or after opening parentheses
-			if j == 0 {
-				continue
-			}
-			if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
-				continue
-			}
-			// replace all whitespace with blanks
-			ch = ' '
-		case ',':
-			comma = j
-		case ')', '}', ']':
-			// remove any trailing comma
-			if comma >= 0 {
-				j = comma
-			}
-			// remove any trailing whitespace
-			if j > 0 && buf[j-1] == ' ' {
-				j--
-			}
-		default:
-			comma = -1
-		}
-		buf[j] = ch
-		j++
-	}
-	// remove trailing blank, if any
-	if j > 0 && buf[j-1] == ' ' {
-		j--
-	}
-	return string(buf[:j])
-}
-
 type PageInfo struct {
 	Dirname  string // directory containing the package
 	Err      error  // error or nil
@@ -350,321 +261,6 @@
 	return pathpkg.Clean("/pkg/"+s) + "/#" + ident
 }
 
-func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
-	var buf bytes.Buffer
-	for _, eg := range info.Examples {
-		name := stripExampleSuffix(eg.Name)
-
-		if name != funcName {
-			continue
-		}
-
-		// print code
-		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
-		code := p.node_htmlFunc(info, cnode, true)
-		out := eg.Output
-		wholeFile := true
-
-		// Additional formatting if this is a function body.
-		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
-			wholeFile = false
-			// remove surrounding braces
-			code = code[1 : n-1]
-			// unindent
-			code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
-			// remove output comment
-			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
-				code = strings.TrimSpace(code[:loc[0]])
-			}
-		}
-
-		// Write out the playground code in standard Go style
-		// (use tabs, no comment highlight, etc).
-		play := ""
-		if eg.Play != nil && p.ShowPlayground {
-			var buf bytes.Buffer
-			eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
-			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
-				log.Print(err)
-			} else {
-				play = buf.String()
-			}
-		}
-
-		// Drop output, as the output comment will appear in the code.
-		if wholeFile && play == "" {
-			out = ""
-		}
-
-		if p.ExampleHTML == nil {
-			out = ""
-			return ""
-		}
-
-		err := p.ExampleHTML.Execute(&buf, struct {
-			Name, Doc, Code, Play, Output string
-			GoogleCN                      bool
-		}{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
-		if err != nil {
-			log.Print(err)
-		}
-	}
-	return buf.String()
-}
-
-func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
-	if len(cg) == 0 {
-		return cg
-	}
-
-	for i := range cg {
-		if !strings.HasPrefix(cg[i].Text(), "+build ") {
-			// Found the first non-build tag, return from here until the end
-			// of the slice.
-			return cg[i:]
-		}
-	}
-
-	// There weren't any non-build tags, return an empty slice.
-	return []*ast.CommentGroup{}
-}
-
-// example_nameFunc takes an example function name and returns its display
-// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
-func (p *Presentation) example_nameFunc(s string) string {
-	name, suffix := splitExampleName(s)
-	// replace _ with . for method names
-	name = strings.Replace(name, "_", ".", 1)
-	// use "Package" if no name provided
-	if name == "" {
-		name = "Package"
-	}
-	return name + suffix
-}
-
-// example_suffixFunc takes an example function name and returns its suffix in
-// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
-func (p *Presentation) example_suffixFunc(name string) string {
-	_, suffix := splitExampleName(name)
-	return suffix
-}
-
 func noteTitle(note string) string {
 	return strings.Title(strings.ToLower(note))
 }
-
-func startsWithUppercase(s string) bool {
-	r, _ := utf8.DecodeRuneInString(s)
-	return unicode.IsUpper(r)
-}
-
-var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
-
-// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
-// while keeping uppercase Braz in Foo_Braz.
-func stripExampleSuffix(name string) string {
-	if i := strings.LastIndex(name, "_"); i != -1 {
-		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
-			name = name[:i]
-		}
-	}
-	return name
-}
-
-func splitExampleName(s string) (name, suffix string) {
-	i := strings.LastIndex(s, "_")
-	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
-		name = s[:i]
-		suffix = " (" + strings.Title(s[i+1:]) + ")"
-		return
-	}
-	name = s
-	return
-}
-
-// replaceLeadingIndentation replaces oldIndent at the beginning of each line
-// with newIndent. This is used for formatting examples. Raw strings that
-// span multiple lines are handled specially: oldIndent is not removed (since
-// go/printer will not add any indentation there), but newIndent is added
-// (since we may still want leading indentation).
-func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
-	// Handle indent at the beginning of the first line. After this, we handle
-	// indentation only after a newline.
-	var buf bytes.Buffer
-	if strings.HasPrefix(body, oldIndent) {
-		buf.WriteString(newIndent)
-		body = body[len(oldIndent):]
-	}
-
-	// Use a state machine to keep track of whether we're in a string or
-	// rune literal while we process the rest of the code.
-	const (
-		codeState = iota
-		runeState
-		interpretedStringState
-		rawStringState
-	)
-	searchChars := []string{
-		"'\"`\n", // codeState
-		`\'`,     // runeState
-		`\"`,     // interpretedStringState
-		"`\n",    // rawStringState
-		// newlineState does not need to search
-	}
-	state := codeState
-	for {
-		i := strings.IndexAny(body, searchChars[state])
-		if i < 0 {
-			buf.WriteString(body)
-			break
-		}
-		c := body[i]
-		buf.WriteString(body[:i+1])
-		body = body[i+1:]
-		switch state {
-		case codeState:
-			switch c {
-			case '\'':
-				state = runeState
-			case '"':
-				state = interpretedStringState
-			case '`':
-				state = rawStringState
-			case '\n':
-				if strings.HasPrefix(body, oldIndent) {
-					buf.WriteString(newIndent)
-					body = body[len(oldIndent):]
-				}
-			}
-
-		case runeState:
-			switch c {
-			case '\\':
-				r, size := utf8.DecodeRuneInString(body)
-				buf.WriteRune(r)
-				body = body[size:]
-			case '\'':
-				state = codeState
-			}
-
-		case interpretedStringState:
-			switch c {
-			case '\\':
-				r, size := utf8.DecodeRuneInString(body)
-				buf.WriteRune(r)
-				body = body[size:]
-			case '"':
-				state = codeState
-			}
-
-		case rawStringState:
-			switch c {
-			case '`':
-				state = codeState
-			case '\n':
-				buf.WriteString(newIndent)
-			}
-		}
-	}
-	return buf.String()
-}
-
-// writeNode writes the AST node x to w.
-//
-// The provided fset must be non-nil. The pageInfo is optional. If
-// present, the pageInfo is used to add comments to struct fields to
-// say which version of Go introduced them.
-func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
-	// convert trailing tabs into spaces using a tconv filter
-	// to ensure a good outcome in most browsers (there may still
-	// be tabs in comments and strings, but converting those into
-	// the right number of spaces is much harder)
-	//
-	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
-	//           with an another printer mode (which is more efficiently
-	//           implemented in the printer than here with another layer)
-
-	var pkgName, structName string
-	var apiInfo pkgAPIVersions
-	if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
-		p.Corpus != nil &&
-		gd.Tok == token.TYPE && len(gd.Specs) != 0 {
-		pkgName = pageInfo.PDoc.ImportPath
-		if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
-			if _, ok := ts.Type.(*ast.StructType); ok {
-				structName = ts.Name.Name
-			}
-		}
-		apiInfo = p.Corpus.pkgAPIInfo[pkgName]
-	}
-
-	var out = w
-	var buf bytes.Buffer
-	if structName != "" {
-		out = &buf
-	}
-
-	mode := printer.TabIndent | printer.UseSpaces
-	err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(TabSpacer(out, p.TabWidth), fset, x)
-	if err != nil {
-		log.Print(err)
-	}
-
-	// Add comments to struct fields saying which Go version introduced them.
-	if structName != "" {
-		fieldSince := apiInfo.fieldSince[structName]
-		typeSince := apiInfo.typeSince[structName]
-		// Add/rewrite comments on struct fields to note which Go version added them.
-		var buf2 bytes.Buffer
-		buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
-		bs := bufio.NewScanner(&buf)
-		for bs.Scan() {
-			line := bs.Bytes()
-			field := firstIdent(line)
-			var since string
-			if field != "" {
-				since = fieldSince[field]
-				if since != "" && since == typeSince {
-					// Don't highlight field versions if they were the
-					// same as the struct itself.
-					since = ""
-				}
-			}
-			if since == "" {
-				buf2.Write(line)
-			} else {
-				if bytes.Contains(line, slashSlash) {
-					line = bytes.TrimRight(line, " \t.")
-					buf2.Write(line)
-					buf2.WriteString("; added in Go ")
-				} else {
-					buf2.Write(line)
-					buf2.WriteString(" // Go ")
-				}
-				buf2.WriteString(since)
-			}
-			buf2.WriteByte('\n')
-		}
-		w.Write(buf2.Bytes())
-	}
-}
-
-var slashSlash = []byte("//")
-
-// WriteNode writes x to w.
-// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
-func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
-	p.writeNode(w, nil, fset, x)
-}
-
-// firstIdent returns the first identifier in x.
-// This actually parses "identifiers" that begin with numbers too, but we
-// never feed it such input, so it's fine.
-func firstIdent(x []byte) string {
-	x = bytes.TrimSpace(x)
-	i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
-	if i == -1 {
-		return string(x)
-	}
-	return string(x[:i])
-}