cmd/golangorg/godoc: delete packages

The godoc package and the packages inside (an exact copy of
x/tools/godoc) are being removed from x/website/cmd/golangorg
because the package should not be in the folder used to build
the binary when it's being used as a library. Also, all the work
to turn documentation-rendering into a library separate from
x/website and the godoc binary will be done in x/tools/godoc.
Over time files and packages that are solely used by the website
will be moved back into this repo. All corresponding path name
changes were made to all files importing the godoc/... packages.

Updates golang/go#29206

Change-Id: I13740b66b967fc413eed7e90bff54f859cf36150
Reviewed-on: https://go-review.googlesource.com/c/159737
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/golangorg/godoc/analysis/README b/cmd/golangorg/godoc/analysis/README
deleted file mode 100644
index d3e732e..0000000
--- a/cmd/golangorg/godoc/analysis/README
+++ /dev/null
@@ -1,111 +0,0 @@
-
-Type and Pointer Analysis to-do list
-====================================
-
-Alan Donovan <adonovan@google.com>
-
-
-Overall design
---------------
-
-We should re-run the type and pointer analyses periodically,
-as we do with the indexer.
-
-Version skew: how to mitigate the bad effects of stale URLs in old pages?
-We could record the file's length/CRC32/mtime in the go/loader, and
-refuse to decorate it with links unless they match at serving time.
-
-Use the VFS mechanism when (a) enumerating packages and (b) loading
-them.  (Requires planned changes to go/loader.)
-
-Future work: shard this using map/reduce for larger corpora.
-
-Testing: how does one test that a web page "looks right"?
-
-
-Bugs
-----
-
-(*ssa.Program).Create requires transitively error-free packages.  We
-can make this more robust by making the requirement transitively free
-of "hard" errors; soft errors are fine.
-
-Markup of compiler errors is slightly buggy because they overlap with
-other selections (e.g. Idents).  Fix.
-
-
-User Interface
---------------
-
-CALLGRAPH:
-- Add a search box: given a search node, expand path from each entry
-  point to it.
-- Cause hovering over a given node to highlight that node, and all
-  nodes that are logically identical to it.
-- Initially expand the callgraph trees (but not their toggle divs).
-
-CALLEES:
-- The '(' links are not very discoverable.  Highlight them?
-
-Type info:
-- In the source viewer's lower pane, use a toggle div around the
-  IMPLEMENTS and METHODSETS lists, like we do in the package view.
-  Only expand them initially if short.
-- Include IMPLEMENTS and METHOD SETS information in search index.
-- URLs in IMPLEMENTS/METHOD SETS always link to source, even from the
-  package docs view.  This makes sense for links to non-exported
-  types, but links to exported types and funcs should probably go to
-  other package docs.
-- Suppress toggle divs for empty method sets.
-
-Misc:
-- The [X] button in the lower pane is subject to scrolling.
-- Should the lower pane be floating?  An iframe?
-  When we change document.location by clicking on a link, it will go away.
-  How do we prevent that (a la Gmail's chat windows)?
-- Progress/status: for each file, display its analysis status, one of:
-   - not in analysis scope
-   - type analysis running...
-   - type analysis complete
-     (+ optionally: there were type errors in this file)
-   And if PTA requested:
-   - type analysis complete; PTA not attempted due to type errors
-   - PTA running...
-   - PTA complete
-- Scroll the selection into view, e.g. the vertical center, or better
-  still, under the pointer (assuming we have a mouse).
-
-
-More features
--------------
-
-Display the REFERRERS relation?  (Useful but potentially large.)
-
-Display the INSTANTIATIONS relation? i.e. given a type T, show the set of
-syntactic constructs that can instantiate it:
-    var x T
-    x := T{...}
-    x = new(T)
-    x = make([]T, n)
-    etc
-    + all INSTANTIATIONS of all S defined as struct{t T} or [n]T
-(Potentially a lot of information.)
-(Add this to guru too.)
-
-
-Optimisations
--------------
-
-Each call to addLink takes a (per-file) lock.  The locking is
-fine-grained so server latency isn't terrible, but overall it makes
-the link computation quite slow.  Batch update might be better.
-
-Memory usage is now about 1.5GB for GOROOT + go.tools.  It used to be 700MB.
-
-Optimize for time and space.  The main slowdown is the network I/O
-time caused by an increase in page size of about 3x: about 2x from
-HTML, and 0.7--2.1x from JSON (unindented vs indented).  The JSON
-contains a lot of filenames (e.g. 820 copies of 16 distinct
-filenames).  20% of the HTML is L%d spans (now disabled).  The HTML
-also contains lots of tooltips for long struct/interface types.
-De-dup or just abbreviate?  The actual formatting is very fast.
diff --git a/cmd/golangorg/godoc/analysis/analysis.go b/cmd/golangorg/godoc/analysis/analysis.go
deleted file mode 100644
index e393004..0000000
--- a/cmd/golangorg/godoc/analysis/analysis.go
+++ /dev/null
@@ -1,613 +0,0 @@
-// Copyright 2014 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.
-
-// Package analysis performs type and pointer analysis
-// and generates mark-up for the Go source view.
-//
-// The Run method populates a Result object by running type and
-// (optionally) pointer analysis.  The Result object is thread-safe
-// and at all times may be accessed by a serving thread, even as it is
-// progressively populated as analysis facts are derived.
-//
-// The Result is a mapping from each godoc file URL
-// (e.g. /src/fmt/print.go) to information about that file.  The
-// information is a list of HTML markup links and a JSON array of
-// structured data values.  Some of the links call client-side
-// JavaScript functions that index this array.
-//
-// The analysis computes mark-up for the following relations:
-//
-// IMPORTS: for each ast.ImportSpec, the package that it denotes.
-//
-// RESOLUTION: for each ast.Ident, its kind and type, and the location
-// of its definition.
-//
-// METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type,
-// its method-set, the set of interfaces it implements or is
-// implemented by, and its size/align values.
-//
-// CALLERS, CALLEES: for each function declaration ('func' token), its
-// callers, and for each call-site ('(' token), its callees.
-//
-// CALLGRAPH: the package docs include an interactive viewer for the
-// intra-package call graph of "fmt".
-//
-// CHANNEL PEERS: for each channel operation make/<-/close, the set of
-// other channel ops that alias the same channel(s).
-//
-// ERRORS: for each locus of a frontend (scanner/parser/type) error, the
-// location is highlighted in red and hover text provides the compiler
-// error message.
-//
-package analysis // import "golang.org/x/website/cmd/golangorg/godoc/analysis"
-
-import (
-	"fmt"
-	"go/build"
-	"go/scanner"
-	"go/token"
-	"go/types"
-	"html"
-	"io"
-	"log"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"sync"
-
-	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/go/pointer"
-	"golang.org/x/tools/go/ssa"
-	"golang.org/x/tools/go/ssa/ssautil"
-)
-
-// -- links ------------------------------------------------------------
-
-// A Link is an HTML decoration of the bytes [Start, End) of a file.
-// Write is called before/after those bytes to emit the mark-up.
-type Link interface {
-	Start() int
-	End() int
-	Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature
-}
-
-// An <a> element.
-type aLink struct {
-	start, end int    // =godoc.Segment
-	title      string // hover text
-	onclick    string // JS code (NB: trusted)
-	href       string // URL     (NB: trusted)
-}
-
-func (a aLink) Start() int { return a.start }
-func (a aLink) End() int   { return a.end }
-func (a aLink) Write(w io.Writer, _ int, start bool) {
-	if start {
-		fmt.Fprintf(w, `<a title='%s'`, html.EscapeString(a.title))
-		if a.onclick != "" {
-			fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick))
-		}
-		if a.href != "" {
-			// TODO(adonovan): I think that in principle, a.href must first be
-			// url.QueryEscape'd, but if I do that, a leading slash becomes "%2F",
-			// which causes the browser to treat the path as relative, not absolute.
-			// WTF?
-			fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href))
-		}
-		fmt.Fprintf(w, ">")
-	} else {
-		fmt.Fprintf(w, "</a>")
-	}
-}
-
-// An <a class='error'> element.
-type errorLink struct {
-	start int
-	msg   string
-}
-
-func (e errorLink) Start() int { return e.start }
-func (e errorLink) End() int   { return e.start + 1 }
-
-func (e errorLink) Write(w io.Writer, _ int, start bool) {
-	// <span> causes havoc, not sure why, so use <a>.
-	if start {
-		fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg))
-	} else {
-		fmt.Fprintf(w, "</a>")
-	}
-}
-
-// -- fileInfo ---------------------------------------------------------
-
-// FileInfo holds analysis information for the source file view.
-// Clients must not mutate it.
-type FileInfo struct {
-	Data  []interface{} // JSON serializable values
-	Links []Link        // HTML link markup
-}
-
-// A fileInfo is the server's store of hyperlinks and JSON data for a
-// particular file.
-type fileInfo struct {
-	mu        sync.Mutex
-	data      []interface{} // JSON objects
-	links     []Link
-	sorted    bool
-	hasErrors bool // TODO(adonovan): surface this in the UI
-}
-
-// addLink adds a link to the Go source file fi.
-func (fi *fileInfo) addLink(link Link) {
-	fi.mu.Lock()
-	fi.links = append(fi.links, link)
-	fi.sorted = false
-	if _, ok := link.(errorLink); ok {
-		fi.hasErrors = true
-	}
-	fi.mu.Unlock()
-}
-
-// addData adds the structured value x to the JSON data for the Go
-// source file fi.  Its index is returned.
-func (fi *fileInfo) addData(x interface{}) int {
-	fi.mu.Lock()
-	index := len(fi.data)
-	fi.data = append(fi.data, x)
-	fi.mu.Unlock()
-	return index
-}
-
-// get returns the file info in external form.
-// Callers must not mutate its fields.
-func (fi *fileInfo) get() FileInfo {
-	var r FileInfo
-	// Copy slices, to avoid races.
-	fi.mu.Lock()
-	r.Data = append(r.Data, fi.data...)
-	if !fi.sorted {
-		sort.Sort(linksByStart(fi.links))
-		fi.sorted = true
-	}
-	r.Links = append(r.Links, fi.links...)
-	fi.mu.Unlock()
-	return r
-}
-
-// PackageInfo holds analysis information for the package view.
-// Clients must not mutate it.
-type PackageInfo struct {
-	CallGraph      []*PCGNodeJSON
-	CallGraphIndex map[string]int
-	Types          []*TypeInfoJSON
-}
-
-type pkgInfo struct {
-	mu             sync.Mutex
-	callGraph      []*PCGNodeJSON
-	callGraphIndex map[string]int  // keys are (*ssa.Function).RelString()
-	types          []*TypeInfoJSON // type info for exported types
-}
-
-func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) {
-	pi.mu.Lock()
-	pi.callGraph = callGraph
-	pi.callGraphIndex = callGraphIndex
-	pi.mu.Unlock()
-}
-
-func (pi *pkgInfo) addType(t *TypeInfoJSON) {
-	pi.mu.Lock()
-	pi.types = append(pi.types, t)
-	pi.mu.Unlock()
-}
-
-// get returns the package info in external form.
-// Callers must not mutate its fields.
-func (pi *pkgInfo) get() PackageInfo {
-	var r PackageInfo
-	// Copy slices, to avoid races.
-	pi.mu.Lock()
-	r.CallGraph = append(r.CallGraph, pi.callGraph...)
-	r.CallGraphIndex = pi.callGraphIndex
-	r.Types = append(r.Types, pi.types...)
-	pi.mu.Unlock()
-	return r
-}
-
-// -- Result -----------------------------------------------------------
-
-// Result contains the results of analysis.
-// The result contains a mapping from filenames to a set of HTML links
-// and JavaScript data referenced by the links.
-type Result struct {
-	mu        sync.Mutex           // guards maps (but not their contents)
-	status    string               // global analysis status
-	fileInfos map[string]*fileInfo // keys are godoc file URLs
-	pkgInfos  map[string]*pkgInfo  // keys are import paths
-}
-
-// fileInfo returns the fileInfo for the specified godoc file URL,
-// constructing it as needed.  Thread-safe.
-func (res *Result) fileInfo(url string) *fileInfo {
-	res.mu.Lock()
-	fi, ok := res.fileInfos[url]
-	if !ok {
-		if res.fileInfos == nil {
-			res.fileInfos = make(map[string]*fileInfo)
-		}
-		fi = new(fileInfo)
-		res.fileInfos[url] = fi
-	}
-	res.mu.Unlock()
-	return fi
-}
-
-// Status returns a human-readable description of the current analysis status.
-func (res *Result) Status() string {
-	res.mu.Lock()
-	defer res.mu.Unlock()
-	return res.status
-}
-
-func (res *Result) setStatusf(format string, args ...interface{}) {
-	res.mu.Lock()
-	res.status = fmt.Sprintf(format, args...)
-	log.Printf(format, args...)
-	res.mu.Unlock()
-}
-
-// FileInfo returns new slices containing opaque JSON values and the
-// HTML link markup for the specified godoc file URL.  Thread-safe.
-// Callers must not mutate the elements.
-// It returns "zero" if no data is available.
-//
-func (res *Result) FileInfo(url string) (fi FileInfo) {
-	return res.fileInfo(url).get()
-}
-
-// pkgInfo returns the pkgInfo for the specified import path,
-// constructing it as needed.  Thread-safe.
-func (res *Result) pkgInfo(importPath string) *pkgInfo {
-	res.mu.Lock()
-	pi, ok := res.pkgInfos[importPath]
-	if !ok {
-		if res.pkgInfos == nil {
-			res.pkgInfos = make(map[string]*pkgInfo)
-		}
-		pi = new(pkgInfo)
-		res.pkgInfos[importPath] = pi
-	}
-	res.mu.Unlock()
-	return pi
-}
-
-// PackageInfo returns new slices of JSON values for the callgraph and
-// type info for the specified package.  Thread-safe.
-// Callers must not mutate its fields.
-// PackageInfo returns "zero" if no data is available.
-//
-func (res *Result) PackageInfo(importPath string) PackageInfo {
-	return res.pkgInfo(importPath).get()
-}
-
-// -- analysis ---------------------------------------------------------
-
-type analysis struct {
-	result    *Result
-	prog      *ssa.Program
-	ops       []chanOp       // all channel ops in program
-	allNamed  []*types.Named // all "defined" (formerly "named") types in the program
-	ptaConfig pointer.Config
-	path2url  map[string]string // maps openable path to godoc file URL (/src/fmt/print.go)
-	pcgs      map[*ssa.Package]*packageCallGraph
-}
-
-// fileAndOffset returns the file and offset for a given pos.
-func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) {
-	return a.fileAndOffsetPosn(a.prog.Fset.Position(pos))
-}
-
-// fileAndOffsetPosn returns the file and offset for a given position.
-func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) {
-	url := a.path2url[posn.Filename]
-	return a.result.fileInfo(url), posn.Offset
-}
-
-// posURL returns the URL of the source extent [pos, pos+len).
-func (a *analysis) posURL(pos token.Pos, len int) string {
-	if pos == token.NoPos {
-		return ""
-	}
-	posn := a.prog.Fset.Position(pos)
-	url := a.path2url[posn.Filename]
-	return fmt.Sprintf("%s?s=%d:%d#L%d",
-		url, posn.Offset, posn.Offset+len, posn.Line)
-}
-
-// ----------------------------------------------------------------------
-
-// Run runs program analysis and computes the resulting markup,
-// populating *result in a thread-safe manner, first with type
-// information then later with pointer analysis information if
-// enabled by the pta flag.
-//
-func Run(pta bool, result *Result) {
-	conf := loader.Config{
-		AllowErrors: true,
-	}
-
-	// Silence the default error handler.
-	// Don't print all errors; we'll report just
-	// one per errant package later.
-	conf.TypeChecker.Error = func(e error) {}
-
-	var roots, args []string // roots[i] ends with os.PathSeparator
-
-	// Enumerate packages in $GOROOT.
-	root := filepath.Join(build.Default.GOROOT, "src") + string(os.PathSeparator)
-	roots = append(roots, root)
-	args = allPackages(root)
-	log.Printf("GOROOT=%s: %s\n", root, args)
-
-	// Enumerate packages in $GOPATH.
-	for i, dir := range filepath.SplitList(build.Default.GOPATH) {
-		root := filepath.Join(dir, "src") + string(os.PathSeparator)
-		roots = append(roots, root)
-		pkgs := allPackages(root)
-		log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs)
-		args = append(args, pkgs...)
-	}
-
-	// Uncomment to make startup quicker during debugging.
-	//args = []string{"golang.org/x/website/cmd/golangorg"}
-	//args = []string{"fmt"}
-
-	if _, err := conf.FromArgs(args, true); err != nil {
-		// TODO(adonovan): degrade gracefully, not fail totally.
-		// (The crippling case is a parse error in an external test file.)
-		result.setStatusf("Analysis failed: %s.", err) // import error
-		return
-	}
-
-	result.setStatusf("Loading and type-checking packages...")
-	iprog, err := conf.Load()
-	if iprog != nil {
-		// Report only the first error of each package.
-		for _, info := range iprog.AllPackages {
-			for _, err := range info.Errors {
-				fmt.Fprintln(os.Stderr, err)
-				break
-			}
-		}
-		log.Printf("Loaded %d packages.", len(iprog.AllPackages))
-	}
-	if err != nil {
-		result.setStatusf("Loading failed: %s.\n", err)
-		return
-	}
-
-	// Create SSA-form program representation.
-	// Only the transitively error-free packages are used.
-	prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug)
-
-	// Create a "testmain" package for each package with tests.
-	for _, pkg := range prog.AllPackages() {
-		if testmain := prog.CreateTestMainPackage(pkg); testmain != nil {
-			log.Printf("Adding tests for %s", pkg.Pkg.Path())
-		}
-	}
-
-	// Build SSA code for bodies of all functions in the whole program.
-	result.setStatusf("Constructing SSA form...")
-	prog.Build()
-	log.Print("SSA construction complete")
-
-	a := analysis{
-		result: result,
-		prog:   prog,
-		pcgs:   make(map[*ssa.Package]*packageCallGraph),
-	}
-
-	// Build a mapping from openable filenames to godoc file URLs,
-	// i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src.
-	a.path2url = make(map[string]string)
-	for _, info := range iprog.AllPackages {
-	nextfile:
-		for _, f := range info.Files {
-			if f.Pos() == 0 {
-				continue // e.g. files generated by cgo
-			}
-			abs := iprog.Fset.File(f.Pos()).Name()
-			// Find the root to which this file belongs.
-			for _, root := range roots {
-				rel := strings.TrimPrefix(abs, root)
-				if len(rel) < len(abs) {
-					a.path2url[abs] = "/src/" + filepath.ToSlash(rel)
-					continue nextfile
-				}
-			}
-
-			log.Printf("Can't locate file %s (package %q) beneath any root",
-				abs, info.Pkg.Path())
-		}
-	}
-
-	// Add links for scanner, parser, type-checker errors.
-	// TODO(adonovan): fix: these links can overlap with
-	// identifier markup, causing the renderer to emit some
-	// characters twice.
-	errors := make(map[token.Position][]string)
-	for _, info := range iprog.AllPackages {
-		for _, err := range info.Errors {
-			switch err := err.(type) {
-			case types.Error:
-				posn := a.prog.Fset.Position(err.Pos)
-				errors[posn] = append(errors[posn], err.Msg)
-			case scanner.ErrorList:
-				for _, e := range err {
-					errors[e.Pos] = append(errors[e.Pos], e.Msg)
-				}
-			default:
-				log.Printf("Package %q has error (%T) without position: %v\n",
-					info.Pkg.Path(), err, err)
-			}
-		}
-	}
-	for posn, errs := range errors {
-		fi, offset := a.fileAndOffsetPosn(posn)
-		fi.addLink(errorLink{
-			start: offset,
-			msg:   strings.Join(errs, "\n"),
-		})
-	}
-
-	// ---------- type-based analyses ----------
-
-	// Compute the all-pairs IMPLEMENTS relation.
-	// Collect all named types, even local types
-	// (which can have methods via promotion)
-	// and the built-in "error".
-	errorType := types.Universe.Lookup("error").Type().(*types.Named)
-	a.allNamed = append(a.allNamed, errorType)
-	for _, info := range iprog.AllPackages {
-		for _, obj := range info.Defs {
-			if obj, ok := obj.(*types.TypeName); ok {
-				if named, ok := obj.Type().(*types.Named); ok {
-					a.allNamed = append(a.allNamed, named)
-				}
-			}
-		}
-	}
-	log.Print("Computing implements relation...")
-	facts := computeImplements(&a.prog.MethodSets, a.allNamed)
-
-	// Add the type-based analysis results.
-	log.Print("Extracting type info...")
-	for _, info := range iprog.AllPackages {
-		a.doTypeInfo(info, facts)
-	}
-
-	a.visitInstrs(pta)
-
-	result.setStatusf("Type analysis complete.")
-
-	if pta {
-		mainPkgs := ssautil.MainPackages(prog.AllPackages())
-		log.Print("Transitively error-free main packages: ", mainPkgs)
-		a.pointer(mainPkgs)
-	}
-}
-
-// visitInstrs visits all SSA instructions in the program.
-func (a *analysis) visitInstrs(pta bool) {
-	log.Print("Visit instructions...")
-	for fn := range ssautil.AllFunctions(a.prog) {
-		for _, b := range fn.Blocks {
-			for _, instr := range b.Instrs {
-				// CALLEES (static)
-				// (Dynamic calls require pointer analysis.)
-				//
-				// We use the SSA representation to find the static callee,
-				// since in many cases it does better than the
-				// types.Info.{Refs,Selection} information.  For example:
-				//
-				//   defer func(){}()      // static call to anon function
-				//   f := func(){}; f()    // static call to anon function
-				//   f := fmt.Println; f() // static call to named function
-				//
-				// The downside is that we get no static callee information
-				// for packages that (transitively) contain errors.
-				if site, ok := instr.(ssa.CallInstruction); ok {
-					if callee := site.Common().StaticCallee(); callee != nil {
-						// TODO(adonovan): callgraph: elide wrappers.
-						// (Do static calls ever go to wrappers?)
-						if site.Common().Pos() != token.NoPos {
-							a.addCallees(site, []*ssa.Function{callee})
-						}
-					}
-				}
-
-				if !pta {
-					continue
-				}
-
-				// CHANNEL PEERS
-				// Collect send/receive/close instructions in the whole ssa.Program.
-				for _, op := range chanOps(instr) {
-					a.ops = append(a.ops, op)
-					a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query
-				}
-			}
-		}
-	}
-	log.Print("Visit instructions complete")
-}
-
-// pointer runs the pointer analysis.
-func (a *analysis) pointer(mainPkgs []*ssa.Package) {
-	// Run the pointer analysis and build the complete callgraph.
-	a.ptaConfig.Mains = mainPkgs
-	a.ptaConfig.BuildCallGraph = true
-	a.ptaConfig.Reflection = false // (for now)
-
-	a.result.setStatusf("Pointer analysis running...")
-
-	ptares, err := pointer.Analyze(&a.ptaConfig)
-	if err != nil {
-		// If this happens, it indicates a bug.
-		a.result.setStatusf("Pointer analysis failed: %s.", err)
-		return
-	}
-	log.Print("Pointer analysis complete.")
-
-	// Add the results of pointer analysis.
-
-	a.result.setStatusf("Computing channel peers...")
-	a.doChannelPeers(ptares.Queries)
-	a.result.setStatusf("Computing dynamic call graph edges...")
-	a.doCallgraph(ptares.CallGraph)
-
-	a.result.setStatusf("Analysis complete.")
-}
-
-type linksByStart []Link
-
-func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() }
-func (a linksByStart) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a linksByStart) Len() int           { return len(a) }
-
-// allPackages returns a new sorted slice of all packages beneath the
-// specified package root directory, e.g. $GOROOT/src or $GOPATH/src.
-// Derived from from go/ssa/stdlib_test.go
-// root must end with os.PathSeparator.
-//
-// TODO(adonovan): use buildutil.AllPackages when the tree thaws.
-func allPackages(root string) []string {
-	var pkgs []string
-	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
-		if info == nil {
-			return nil // non-existent root directory?
-		}
-		if !info.IsDir() {
-			return nil // not a directory
-		}
-		// Prune the search if we encounter any of these names:
-		base := filepath.Base(path)
-		if base == "testdata" || strings.HasPrefix(base, ".") {
-			return filepath.SkipDir
-		}
-		pkg := filepath.ToSlash(strings.TrimPrefix(path, root))
-		switch pkg {
-		case "builtin":
-			return filepath.SkipDir
-		case "":
-			return nil // ignore root of tree
-		}
-		pkgs = append(pkgs, pkg)
-		return nil
-	})
-	return pkgs
-}
diff --git a/cmd/golangorg/godoc/analysis/callgraph.go b/cmd/golangorg/godoc/analysis/callgraph.go
deleted file mode 100644
index 492022d..0000000
--- a/cmd/golangorg/godoc/analysis/callgraph.go
+++ /dev/null
@@ -1,351 +0,0 @@
-// Copyright 2014 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.
-
-package analysis
-
-// This file computes the CALLERS and CALLEES relations from the call
-// graph.  CALLERS/CALLEES information is displayed in the lower pane
-// when a "func" token or ast.CallExpr.Lparen is clicked, respectively.
-
-import (
-	"fmt"
-	"go/ast"
-	"go/token"
-	"go/types"
-	"log"
-	"math/big"
-	"sort"
-
-	"golang.org/x/tools/go/callgraph"
-	"golang.org/x/tools/go/ssa"
-)
-
-// doCallgraph computes the CALLEES and CALLERS relations.
-func (a *analysis) doCallgraph(cg *callgraph.Graph) {
-	log.Print("Deleting synthetic nodes...")
-	// TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically
-	// inefficient and can be (unpredictably) slow.
-	cg.DeleteSyntheticNodes()
-	log.Print("Synthetic nodes deleted")
-
-	// Populate nodes of package call graphs (PCGs).
-	for _, n := range cg.Nodes {
-		a.pcgAddNode(n.Func)
-	}
-	// Within each PCG, sort funcs by name.
-	for _, pcg := range a.pcgs {
-		pcg.sortNodes()
-	}
-
-	calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool)
-	callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool)
-	for _, n := range cg.Nodes {
-		for _, e := range n.Out {
-			if e.Site == nil {
-				continue // a call from a synthetic node such as <root>
-			}
-
-			// Add (site pos, callee) to calledFuncs.
-			// (Dynamic calls only.)
-			callee := e.Callee.Func
-
-			a.pcgAddEdge(n.Func, callee)
-
-			if callee.Synthetic != "" {
-				continue // call of a package initializer
-			}
-
-			if e.Site.Common().StaticCallee() == nil {
-				// dynamic call
-				// (CALLEES information for static calls
-				// is computed using SSA information.)
-				lparen := e.Site.Common().Pos()
-				if lparen != token.NoPos {
-					fns := calledFuncs[e.Site]
-					if fns == nil {
-						fns = make(map[*ssa.Function]bool)
-						calledFuncs[e.Site] = fns
-					}
-					fns[callee] = true
-				}
-			}
-
-			// Add (callee, site) to callingSites.
-			fns := callingSites[callee]
-			if fns == nil {
-				fns = make(map[ssa.CallInstruction]bool)
-				callingSites[callee] = fns
-			}
-			fns[e.Site] = true
-		}
-	}
-
-	// CALLEES.
-	log.Print("Callees...")
-	for site, fns := range calledFuncs {
-		var funcs funcsByPos
-		for fn := range fns {
-			funcs = append(funcs, fn)
-		}
-		sort.Sort(funcs)
-
-		a.addCallees(site, funcs)
-	}
-
-	// CALLERS
-	log.Print("Callers...")
-	for callee, sites := range callingSites {
-		pos := funcToken(callee)
-		if pos == token.NoPos {
-			log.Printf("CALLERS: skipping %s: no pos", callee)
-			continue
-		}
-
-		var this *types.Package // for relativizing names
-		if callee.Pkg != nil {
-			this = callee.Pkg.Pkg
-		}
-
-		// Compute sites grouped by parent, with text and URLs.
-		sitesByParent := make(map[*ssa.Function]sitesByPos)
-		for site := range sites {
-			fn := site.Parent()
-			sitesByParent[fn] = append(sitesByParent[fn], site)
-		}
-		var funcs funcsByPos
-		for fn := range sitesByParent {
-			funcs = append(funcs, fn)
-		}
-		sort.Sort(funcs)
-
-		v := callersJSON{
-			Callee:  callee.String(),
-			Callers: []callerJSON{}, // (JS wants non-nil)
-		}
-		for _, fn := range funcs {
-			caller := callerJSON{
-				Func:  prettyFunc(this, fn),
-				Sites: []anchorJSON{}, // (JS wants non-nil)
-			}
-			sites := sitesByParent[fn]
-			sort.Sort(sites)
-			for _, site := range sites {
-				pos := site.Common().Pos()
-				if pos != token.NoPos {
-					caller.Sites = append(caller.Sites, anchorJSON{
-						Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line),
-						Href: a.posURL(pos, len("(")),
-					})
-				}
-			}
-			v.Callers = append(v.Callers, caller)
-		}
-
-		fi, offset := a.fileAndOffset(pos)
-		fi.addLink(aLink{
-			start:   offset,
-			end:     offset + len("func"),
-			title:   fmt.Sprintf("%d callers", len(sites)),
-			onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)),
-		})
-	}
-
-	// PACKAGE CALLGRAPH
-	log.Print("Package call graph...")
-	for pkg, pcg := range a.pcgs {
-		// Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array.
-		index := make(map[string]int)
-
-		// Treat exported functions (and exported methods of
-		// exported named types) as roots even if they aren't
-		// actually called from outside the package.
-		for i, n := range pcg.nodes {
-			if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() {
-				continue
-			}
-			recv := n.fn.Signature.Recv()
-			if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() {
-				roots := &pcg.nodes[0].edges
-				roots.SetBit(roots, i, 1)
-			}
-			index[n.fn.RelString(pkg.Pkg)] = i
-		}
-
-		json := a.pcgJSON(pcg)
-
-		// TODO(adonovan): pkg.Path() is not unique!
-		// It is possible to declare a non-test package called x_test.
-		a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index)
-	}
-}
-
-// addCallees adds client data and links for the facts that site calls fns.
-func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) {
-	v := calleesJSON{
-		Descr:   site.Common().Description(),
-		Callees: []anchorJSON{}, // (JS wants non-nil)
-	}
-	var this *types.Package // for relativizing names
-	if p := site.Parent().Package(); p != nil {
-		this = p.Pkg
-	}
-
-	for _, fn := range fns {
-		v.Callees = append(v.Callees, anchorJSON{
-			Text: prettyFunc(this, fn),
-			Href: a.posURL(funcToken(fn), len("func")),
-		})
-	}
-
-	fi, offset := a.fileAndOffset(site.Common().Pos())
-	fi.addLink(aLink{
-		start:   offset,
-		end:     offset + len("("),
-		title:   fmt.Sprintf("%d callees", len(v.Callees)),
-		onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)),
-	})
-}
-
-// -- utilities --------------------------------------------------------
-
-// stable order within packages but undefined across packages.
-type funcsByPos []*ssa.Function
-
-func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
-func (a funcsByPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a funcsByPos) Len() int           { return len(a) }
-
-type sitesByPos []ssa.CallInstruction
-
-func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() }
-func (a sitesByPos) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a sitesByPos) Len() int           { return len(a) }
-
-func funcToken(fn *ssa.Function) token.Pos {
-	switch syntax := fn.Syntax().(type) {
-	case *ast.FuncLit:
-		return syntax.Type.Func
-	case *ast.FuncDecl:
-		return syntax.Type.Func
-	}
-	return token.NoPos
-}
-
-// prettyFunc pretty-prints fn for the user interface.
-// TODO(adonovan): return HTML so we have more markup freedom.
-func prettyFunc(this *types.Package, fn *ssa.Function) string {
-	if fn.Parent() != nil {
-		return fmt.Sprintf("%s in %s",
-			types.TypeString(fn.Signature, types.RelativeTo(this)),
-			prettyFunc(this, fn.Parent()))
-	}
-	if fn.Synthetic != "" && fn.Name() == "init" {
-		// (This is the actual initializer, not a declared 'func init').
-		if fn.Pkg.Pkg == this {
-			return "package initializer"
-		}
-		return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path())
-	}
-	return fn.RelString(this)
-}
-
-// -- intra-package callgraph ------------------------------------------
-
-// pcgNode represents a node in the package call graph (PCG).
-type pcgNode struct {
-	fn     *ssa.Function
-	pretty string  // cache of prettyFunc(fn)
-	edges  big.Int // set of callee func indices
-}
-
-// A packageCallGraph represents the intra-package edges of the global call graph.
-// The zeroth node indicates "all external functions".
-type packageCallGraph struct {
-	nodeIndex map[*ssa.Function]int // maps func to node index (a small int)
-	nodes     []*pcgNode            // maps node index to node
-}
-
-// sortNodes populates pcg.nodes in name order and updates the nodeIndex.
-func (pcg *packageCallGraph) sortNodes() {
-	nodes := make([]*pcgNode, 0, len(pcg.nodeIndex))
-	nodes = append(nodes, &pcgNode{fn: nil, pretty: "<external>"})
-	for fn := range pcg.nodeIndex {
-		nodes = append(nodes, &pcgNode{
-			fn:     fn,
-			pretty: prettyFunc(fn.Pkg.Pkg, fn),
-		})
-	}
-	sort.Sort(pcgNodesByPretty(nodes[1:]))
-	for i, n := range nodes {
-		pcg.nodeIndex[n.fn] = i
-	}
-	pcg.nodes = nodes
-}
-
-func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) {
-	var callerIndex int
-	if caller.Pkg == callee.Pkg {
-		// intra-package edge
-		callerIndex = pcg.nodeIndex[caller]
-		if callerIndex < 1 {
-			panic(caller)
-		}
-	}
-	edges := &pcg.nodes[callerIndex].edges
-	edges.SetBit(edges, pcg.nodeIndex[callee], 1)
-}
-
-func (a *analysis) pcgAddNode(fn *ssa.Function) {
-	if fn.Pkg == nil {
-		return
-	}
-	pcg, ok := a.pcgs[fn.Pkg]
-	if !ok {
-		pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)}
-		a.pcgs[fn.Pkg] = pcg
-	}
-	pcg.nodeIndex[fn] = -1
-}
-
-func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) {
-	if callee.Pkg != nil {
-		a.pcgs[callee.Pkg].addEdge(caller, callee)
-	}
-}
-
-// pcgJSON returns a new slice of callgraph JSON values.
-func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON {
-	var nodes []*PCGNodeJSON
-	for _, n := range pcg.nodes {
-
-		// TODO(adonovan): why is there no good way to iterate
-		// over the set bits of a big.Int?
-		var callees []int
-		nbits := n.edges.BitLen()
-		for j := 0; j < nbits; j++ {
-			if n.edges.Bit(j) == 1 {
-				callees = append(callees, j)
-			}
-		}
-
-		var pos token.Pos
-		if n.fn != nil {
-			pos = funcToken(n.fn)
-		}
-		nodes = append(nodes, &PCGNodeJSON{
-			Func: anchorJSON{
-				Text: n.pretty,
-				Href: a.posURL(pos, len("func")),
-			},
-			Callees: callees,
-		})
-	}
-	return nodes
-}
-
-type pcgNodesByPretty []*pcgNode
-
-func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty }
-func (a pcgNodesByPretty) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
-func (a pcgNodesByPretty) Len() int           { return len(a) }
diff --git a/cmd/golangorg/godoc/analysis/implements.go b/cmd/golangorg/godoc/analysis/implements.go
deleted file mode 100644
index 5a29579..0000000
--- a/cmd/golangorg/godoc/analysis/implements.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2014 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.
-
-package analysis
-
-// This file computes the "implements" relation over all pairs of
-// named types in the program.  (The mark-up is done by typeinfo.go.)
-
-// TODO(adonovan): do we want to report implements(C, I) where C and I
-// belong to different packages and at least one is not exported?
-
-import (
-	"go/types"
-	"sort"
-
-	"golang.org/x/tools/go/types/typeutil"
-)
-
-// computeImplements computes the "implements" relation over all pairs
-// of named types in allNamed.
-func computeImplements(cache *typeutil.MethodSetCache, allNamed []*types.Named) map[*types.Named]implementsFacts {
-	// Information about a single type's method set.
-	type msetInfo struct {
-		typ          types.Type
-		mset         *types.MethodSet
-		mask1, mask2 uint64
-	}
-
-	initMsetInfo := func(info *msetInfo, typ types.Type) {
-		info.typ = typ
-		info.mset = cache.MethodSet(typ)
-		for i := 0; i < info.mset.Len(); i++ {
-			name := info.mset.At(i).Obj().Name()
-			info.mask1 |= 1 << methodBit(name[0])
-			info.mask2 |= 1 << methodBit(name[len(name)-1])
-		}
-	}
-
-	// satisfies(T, U) reports whether type T satisfies type U.
-	// U must be an interface.
-	//
-	// Since there are thousands of types (and thus millions of
-	// pairs of types) and types.Assignable(T, U) is relatively
-	// expensive, we compute assignability directly from the
-	// method sets.  (At least one of T and U must be an
-	// interface.)
-	//
-	// We use a trick (thanks gri!) related to a Bloom filter to
-	// quickly reject most tests, which are false.  For each
-	// method set, we precompute a mask, a set of bits, one per
-	// distinct initial byte of each method name.  Thus the mask
-	// for io.ReadWriter would be {'R','W'}.  AssignableTo(T, U)
-	// cannot be true unless mask(T)&mask(U)==mask(U).
-	//
-	// As with a Bloom filter, we can improve precision by testing
-	// additional hashes, e.g. using the last letter of each
-	// method name, so long as the subset mask property holds.
-	//
-	// When analyzing the standard library, there are about 1e6
-	// calls to satisfies(), of which 0.6% return true.  With a
-	// 1-hash filter, 95% of calls avoid the expensive check; with
-	// a 2-hash filter, this grows to 98.2%.
-	satisfies := func(T, U *msetInfo) bool {
-		return T.mask1&U.mask1 == U.mask1 &&
-			T.mask2&U.mask2 == U.mask2 &&
-			containsAllIdsOf(T.mset, U.mset)
-	}
-
-	// Information about a named type N, and perhaps also *N.
-	type namedInfo struct {
-		isInterface bool
-		base        msetInfo // N
-		ptr         msetInfo // *N, iff N !isInterface
-	}
-
-	var infos []namedInfo
-
-	// Precompute the method sets and their masks.
-	for _, N := range allNamed {
-		var info namedInfo
-		initMsetInfo(&info.base, N)
-		_, info.isInterface = N.Underlying().(*types.Interface)
-		if !info.isInterface {
-			initMsetInfo(&info.ptr, types.NewPointer(N))
-		}
-
-		if info.base.mask1|info.ptr.mask1 == 0 {
-			continue // neither N nor *N has methods
-		}
-
-		infos = append(infos, info)
-	}
-
-	facts := make(map[*types.Named]implementsFacts)
-
-	// Test all pairs of distinct named types (T, U).
-	// TODO(adonovan): opt: compute (U, T) at the same time.
-	for t := range infos {
-		T := &infos[t]
-		var to, from, fromPtr []types.Type
-		for u := range infos {
-			if t == u {
-				continue
-			}
-			U := &infos[u]
-			switch {
-			case T.isInterface && U.isInterface:
-				if satisfies(&U.base, &T.base) {
-					to = append(to, U.base.typ)
-				}
-				if satisfies(&T.base, &U.base) {
-					from = append(from, U.base.typ)
-				}
-			case T.isInterface: // U concrete
-				if satisfies(&U.base, &T.base) {
-					to = append(to, U.base.typ)
-				} else if satisfies(&U.ptr, &T.base) {
-					to = append(to, U.ptr.typ)
-				}
-			case U.isInterface: // T concrete
-				if satisfies(&T.base, &U.base) {
-					from = append(from, U.base.typ)
-				} else if satisfies(&T.ptr, &U.base) {
-					fromPtr = append(fromPtr, U.base.typ)
-				}
-			}
-		}
-
-		// Sort types (arbitrarily) to avoid nondeterminism.
-		sort.Sort(typesByString(to))
-		sort.Sort(typesByString(from))
-		sort.Sort(typesByString(fromPtr))
-
-		facts[T.base.typ.(*types.Named)] = implementsFacts{to, from, fromPtr}
-	}
-
-	return facts
-}
-
-type implementsFacts struct {
-	to      []types.Type // named or ptr-to-named types assignable to interface T
-	from    []types.Type // named interfaces assignable from T
-	fromPtr []types.Type // named interfaces assignable only from *T
-}
-
-type typesByString []types.Type
-
-func (p typesByString) Len() int           { return len(p) }
-func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() }
-func (p typesByString) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
-
-// methodBit returns the index of x in [a-zA-Z], or 52 if not found.
-func methodBit(x byte) uint64 {
-	switch {
-	case 'a' <= x && x <= 'z':
-		return uint64(x - 'a')
-	case 'A' <= x && x <= 'Z':
-		return uint64(26 + x - 'A')
-	}
-	return 52 // all other bytes
-}
-
-// containsAllIdsOf reports whether the method identifiers of T are a
-// superset of those in U.  If U belongs to an interface type, the
-// result is equal to types.Assignable(T, U), but is cheaper to compute.
-//
-// TODO(gri): make this a method of *types.MethodSet.
-//
-func containsAllIdsOf(T, U *types.MethodSet) bool {
-	t, tlen := 0, T.Len()
-	u, ulen := 0, U.Len()
-	for t < tlen && u < ulen {
-		tMeth := T.At(t).Obj()
-		uMeth := U.At(u).Obj()
-		tId := tMeth.Id()
-		uId := uMeth.Id()
-		if tId > uId {
-			// U has a method T lacks: fail.
-			return false
-		}
-		if tId < uId {
-			// T has a method U lacks: ignore it.
-			t++
-			continue
-		}
-		// U and T both have a method of this Id.  Check types.
-		if !types.Identical(tMeth.Type(), uMeth.Type()) {
-			return false // type mismatch
-		}
-		u++
-		t++
-	}
-	return u == ulen
-}
diff --git a/cmd/golangorg/godoc/analysis/json.go b/cmd/golangorg/godoc/analysis/json.go
deleted file mode 100644
index f897618..0000000
--- a/cmd/golangorg/godoc/analysis/json.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2014 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.
-
-package analysis
-
-// This file defines types used by client-side JavaScript.
-
-type anchorJSON struct {
-	Text string // HTML
-	Href string // URL
-}
-
-type commOpJSON struct {
-	Op anchorJSON
-	Fn string
-}
-
-// JavaScript's onClickComm() expects a commJSON.
-type commJSON struct {
-	Ops []commOpJSON
-}
-
-// Indicates one of these forms of fact about a type T:
-// T "is implemented by <ByKind> type <Other>"  (ByKind != "", e.g. "array")
-// T "implements <Other>"                       (ByKind == "")
-type implFactJSON struct {
-	ByKind string `json:",omitempty"`
-	Other  anchorJSON
-}
-
-// Implements facts are grouped by form, for ease of reading.
-type implGroupJSON struct {
-	Descr string
-	Facts []implFactJSON
-}
-
-// JavaScript's onClickIdent() expects a TypeInfoJSON.
-type TypeInfoJSON struct {
-	Name        string // type name
-	Size, Align int64
-	Methods     []anchorJSON
-	ImplGroups  []implGroupJSON
-}
-
-// JavaScript's onClickCallees() expects a calleesJSON.
-type calleesJSON struct {
-	Descr   string
-	Callees []anchorJSON // markup for called function
-}
-
-type callerJSON struct {
-	Func  string
-	Sites []anchorJSON
-}
-
-// JavaScript's onClickCallers() expects a callersJSON.
-type callersJSON struct {
-	Callee  string
-	Callers []callerJSON
-}
-
-// JavaScript's cgAddChild requires a global array of PCGNodeJSON
-// called CALLGRAPH, representing the intra-package call graph.
-// The first element is special and represents "all external callers".
-type PCGNodeJSON struct {
-	Func    anchorJSON
-	Callees []int // indices within CALLGRAPH of nodes called by this one
-}
diff --git a/cmd/golangorg/godoc/analysis/peers.go b/cmd/golangorg/godoc/analysis/peers.go
deleted file mode 100644
index a742f06..0000000
--- a/cmd/golangorg/godoc/analysis/peers.go
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2014 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.
-
-package analysis
-
-// This file computes the channel "peers" relation over all pairs of
-// channel operations in the program.  The peers are displayed in the
-// lower pane when a channel operation (make, <-, close) is clicked.
-
-// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too,
-// then enable reflection in PTA.
-
-import (
-	"fmt"
-	"go/token"
-	"go/types"
-
-	"golang.org/x/tools/go/pointer"
-	"golang.org/x/tools/go/ssa"
-)
-
-func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) {
-	addSendRecv := func(j *commJSON, op chanOp) {
-		j.Ops = append(j.Ops, commOpJSON{
-			Op: anchorJSON{
-				Text: op.mode,
-				Href: a.posURL(op.pos, op.len),
-			},
-			Fn: prettyFunc(nil, op.fn),
-		})
-	}
-
-	// Build an undirected bipartite multigraph (binary relation)
-	// of MakeChan ops and send/recv/close ops.
-	//
-	// TODO(adonovan): opt: use channel element types to partition
-	// the O(n^2) problem into subproblems.
-	aliasedOps := make(map[*ssa.MakeChan][]chanOp)
-	opToMakes := make(map[chanOp][]*ssa.MakeChan)
-	for _, op := range a.ops {
-		// Combine the PT sets from all contexts.
-		var makes []*ssa.MakeChan // aliased ops
-		ptr, ok := ptsets[op.ch]
-		if !ok {
-			continue // e.g. channel op in dead code
-		}
-		for _, label := range ptr.PointsTo().Labels() {
-			makechan, ok := label.Value().(*ssa.MakeChan)
-			if !ok {
-				continue // skip intrinsically-created channels for now
-			}
-			if makechan.Pos() == token.NoPos {
-				continue // not possible?
-			}
-			makes = append(makes, makechan)
-			aliasedOps[makechan] = append(aliasedOps[makechan], op)
-		}
-		opToMakes[op] = makes
-	}
-
-	// Now that complete relation is built, build links for ops.
-	for _, op := range a.ops {
-		v := commJSON{
-			Ops: []commOpJSON{}, // (JS wants non-nil)
-		}
-		ops := make(map[chanOp]bool)
-		for _, makechan := range opToMakes[op] {
-			v.Ops = append(v.Ops, commOpJSON{
-				Op: anchorJSON{
-					Text: "made",
-					Href: a.posURL(makechan.Pos()-token.Pos(len("make")),
-						len("make")),
-				},
-				Fn: makechan.Parent().RelString(op.fn.Package().Pkg),
-			})
-			for _, op := range aliasedOps[makechan] {
-				ops[op] = true
-			}
-		}
-		for op := range ops {
-			addSendRecv(&v, op)
-		}
-
-		// Add links for each aliased op.
-		fi, offset := a.fileAndOffset(op.pos)
-		fi.addLink(aLink{
-			start:   offset,
-			end:     offset + op.len,
-			title:   "show channel ops",
-			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
-		})
-	}
-	// Add links for makechan ops themselves.
-	for makechan, ops := range aliasedOps {
-		v := commJSON{
-			Ops: []commOpJSON{}, // (JS wants non-nil)
-		}
-		for _, op := range ops {
-			addSendRecv(&v, op)
-		}
-
-		fi, offset := a.fileAndOffset(makechan.Pos())
-		fi.addLink(aLink{
-			start:   offset - len("make"),
-			end:     offset,
-			title:   "show channel ops",
-			onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)),
-		})
-	}
-}
-
-// -- utilities --------------------------------------------------------
-
-// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState.
-// Derived from cmd/guru/peers.go.
-type chanOp struct {
-	ch   ssa.Value
-	mode string // sent|received|closed
-	pos  token.Pos
-	len  int
-	fn   *ssa.Function
-}
-
-// chanOps returns a slice of all the channel operations in the instruction.
-// Derived from cmd/guru/peers.go.
-func chanOps(instr ssa.Instruction) []chanOp {
-	fn := instr.Parent()
-	var ops []chanOp
-	switch instr := instr.(type) {
-	case *ssa.UnOp:
-		if instr.Op == token.ARROW {
-			// TODO(adonovan): don't assume <-ch; could be 'range ch'.
-			ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn})
-		}
-	case *ssa.Send:
-		ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn})
-	case *ssa.Select:
-		for _, st := range instr.States {
-			mode := "received"
-			if st.Dir == types.SendOnly {
-				mode = "sent"
-			}
-			ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn})
-		}
-	case ssa.CallInstruction:
-		call := instr.Common()
-		if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" {
-			pos := instr.Common().Pos()
-			ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn})
-		}
-	}
-	return ops
-}
diff --git a/cmd/golangorg/godoc/analysis/typeinfo.go b/cmd/golangorg/godoc/analysis/typeinfo.go
deleted file mode 100644
index e57683f..0000000
--- a/cmd/golangorg/godoc/analysis/typeinfo.go
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright 2014 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.
-
-package analysis
-
-// This file computes the markup for information from go/types:
-// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and
-// the IMPLEMENTS relation.
-//
-// IMPORTS links connect import specs to the documentation for the
-// imported package.
-//
-// RESOLUTION links referring identifiers to their defining
-// identifier, and adds tooltips for kind and type.
-//
-// METHOD SETS, size/alignment, and the IMPLEMENTS relation are
-// displayed in the lower pane when a type's defining identifier is
-// clicked.
-
-import (
-	"fmt"
-	"go/types"
-	"reflect"
-	"strconv"
-	"strings"
-
-	"golang.org/x/tools/go/loader"
-	"golang.org/x/tools/go/types/typeutil"
-)
-
-// TODO(adonovan): audit to make sure it's safe on ill-typed packages.
-
-// TODO(adonovan): use same Sizes as loader.Config.
-var sizes = types.StdSizes{WordSize: 8, MaxAlign: 8}
-
-func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) {
-	// We must not assume the corresponding SSA packages were
-	// created (i.e. were transitively error-free).
-
-	// IMPORTS
-	for _, f := range info.Files {
-		// Package decl.
-		fi, offset := a.fileAndOffset(f.Name.Pos())
-		fi.addLink(aLink{
-			start: offset,
-			end:   offset + len(f.Name.Name),
-			title: "Package docs for " + info.Pkg.Path(),
-			// TODO(adonovan): fix: we're putting the untrusted Path()
-			// into a trusted field.  What's the appropriate sanitizer?
-			href: "/pkg/" + info.Pkg.Path(),
-		})
-
-		// Import specs.
-		for _, imp := range f.Imports {
-			// Remove quotes.
-			L := int(imp.End()-imp.Path.Pos()) - len(`""`)
-			path, _ := strconv.Unquote(imp.Path.Value)
-			fi, offset := a.fileAndOffset(imp.Path.Pos())
-			fi.addLink(aLink{
-				start: offset + 1,
-				end:   offset + 1 + L,
-				title: "Package docs for " + path,
-				// TODO(adonovan): fix: we're putting the untrusted path
-				// into a trusted field.  What's the appropriate sanitizer?
-				href: "/pkg/" + path,
-			})
-		}
-	}
-
-	// RESOLUTION
-	qualifier := types.RelativeTo(info.Pkg)
-	for id, obj := range info.Uses {
-		// Position of the object definition.
-		pos := obj.Pos()
-		Len := len(obj.Name())
-
-		// Correct the position for non-renaming import specs.
-		//  import "sync/atomic"
-		//          ^^^^^^^^^^^
-		if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() {
-			// Assume this is a non-renaming import.
-			// NB: not true for degenerate renamings: `import foo "foo"`.
-			pos++
-			Len = len(obj.Imported().Path())
-		}
-
-		if obj.Pkg() == nil {
-			continue // don't mark up built-ins.
-		}
-
-		fi, offset := a.fileAndOffset(id.NamePos)
-		fi.addLink(aLink{
-			start: offset,
-			end:   offset + len(id.Name),
-			title: types.ObjectString(obj, qualifier),
-			href:  a.posURL(pos, Len),
-		})
-	}
-
-	// IMPLEMENTS & METHOD SETS
-	for _, obj := range info.Defs {
-		if obj, ok := obj.(*types.TypeName); ok {
-			if named, ok := obj.Type().(*types.Named); ok {
-				a.namedType(named, implements)
-			}
-		}
-	}
-}
-
-func (a *analysis) namedType(T *types.Named, implements map[*types.Named]implementsFacts) {
-	obj := T.Obj()
-	qualifier := types.RelativeTo(obj.Pkg())
-	v := &TypeInfoJSON{
-		Name:    obj.Name(),
-		Size:    sizes.Sizeof(T),
-		Align:   sizes.Alignof(T),
-		Methods: []anchorJSON{}, // (JS wants non-nil)
-	}
-
-	// addFact adds the fact "is implemented by T" (by) or
-	// "implements T" (!by) to group.
-	addFact := func(group *implGroupJSON, T types.Type, by bool) {
-		Tobj := deref(T).(*types.Named).Obj()
-		var byKind string
-		if by {
-			// Show underlying kind of implementing type,
-			// e.g. "slice", "array", "struct".
-			s := reflect.TypeOf(T.Underlying()).String()
-			byKind = strings.ToLower(strings.TrimPrefix(s, "*types."))
-		}
-		group.Facts = append(group.Facts, implFactJSON{
-			ByKind: byKind,
-			Other: anchorJSON{
-				Href: a.posURL(Tobj.Pos(), len(Tobj.Name())),
-				Text: types.TypeString(T, qualifier),
-			},
-		})
-	}
-
-	// IMPLEMENTS
-	if r, ok := implements[T]; ok {
-		if isInterface(T) {
-			// "T is implemented by <conc>" ...
-			// "T is implemented by <iface>"...
-			// "T implements        <iface>"...
-			group := implGroupJSON{
-				Descr: types.TypeString(T, qualifier),
-			}
-			// Show concrete types first; use two passes.
-			for _, sub := range r.to {
-				if !isInterface(sub) {
-					addFact(&group, sub, true)
-				}
-			}
-			for _, sub := range r.to {
-				if isInterface(sub) {
-					addFact(&group, sub, true)
-				}
-			}
-			for _, super := range r.from {
-				addFact(&group, super, false)
-			}
-			v.ImplGroups = append(v.ImplGroups, group)
-		} else {
-			// T is concrete.
-			if r.from != nil {
-				// "T implements <iface>"...
-				group := implGroupJSON{
-					Descr: types.TypeString(T, qualifier),
-				}
-				for _, super := range r.from {
-					addFact(&group, super, false)
-				}
-				v.ImplGroups = append(v.ImplGroups, group)
-			}
-			if r.fromPtr != nil {
-				// "*C implements <iface>"...
-				group := implGroupJSON{
-					Descr: "*" + types.TypeString(T, qualifier),
-				}
-				for _, psuper := range r.fromPtr {
-					addFact(&group, psuper, false)
-				}
-				v.ImplGroups = append(v.ImplGroups, group)
-			}
-		}
-	}
-
-	// METHOD SETS
-	for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) {
-		meth := sel.Obj().(*types.Func)
-		pos := meth.Pos() // may be 0 for error.Error
-		v.Methods = append(v.Methods, anchorJSON{
-			Href: a.posURL(pos, len(meth.Name())),
-			Text: types.SelectionString(sel, qualifier),
-		})
-	}
-
-	// Since there can be many specs per decl, we
-	// can't attach the link to the keyword 'type'
-	// (as we do with 'func'); we use the Ident.
-	fi, offset := a.fileAndOffset(obj.Pos())
-	fi.addLink(aLink{
-		start:   offset,
-		end:     offset + len(obj.Name()),
-		title:   fmt.Sprintf("type info for %s", obj.Name()),
-		onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)),
-	})
-
-	// Add info for exported package-level types to the package info.
-	if obj.Exported() && isPackageLevel(obj) {
-		// TODO(adonovan): Path is not unique!
-		// It is possible to declare a non-test package called x_test.
-		a.result.pkgInfo(obj.Pkg().Path()).addType(v)
-	}
-}
-
-// -- utilities --------------------------------------------------------
-
-func isInterface(T types.Type) bool { return types.IsInterface(T) }
-
-// deref returns a pointer's element type; otherwise it returns typ.
-func deref(typ types.Type) types.Type {
-	if p, ok := typ.Underlying().(*types.Pointer); ok {
-		return p.Elem()
-	}
-	return typ
-}
-
-// isPackageLevel reports whether obj is a package-level object.
-func isPackageLevel(obj types.Object) bool {
-	return obj.Pkg().Scope().Lookup(obj.Name()) == obj
-}
diff --git a/cmd/golangorg/godoc/corpus.go b/cmd/golangorg/godoc/corpus.go
deleted file mode 100644
index 38e39db..0000000
--- a/cmd/golangorg/godoc/corpus.go
+++ /dev/null
@@ -1,168 +0,0 @@
-// 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.
-
-package godoc
-
-import (
-	"errors"
-	pathpkg "path"
-	"sync"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/analysis"
-	"golang.org/x/website/cmd/golangorg/godoc/util"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// A Corpus holds all the state related to serving and indexing a
-// collection of Go code.
-//
-// Construct a new Corpus with NewCorpus, then modify options,
-// then call its Init method.
-type Corpus struct {
-	fs vfs.FileSystem
-
-	// Verbose logging.
-	Verbose bool
-
-	// IndexEnabled controls whether indexing is enabled.
-	IndexEnabled bool
-
-	// IndexFiles specifies a glob pattern specifying index files.
-	// If not empty, the index is read from these files in sorted
-	// order.
-	IndexFiles string
-
-	// IndexThrottle specifies the indexing throttle value
-	// between 0.0 and 1.0. At 0.0, the indexer always sleeps.
-	// At 1.0, the indexer never sleeps. Because 0.0 is useless
-	// and redundant with setting IndexEnabled to false, the
-	// zero value for IndexThrottle means 0.9.
-	IndexThrottle float64
-
-	// IndexInterval specifies the time to sleep between reindexing
-	// all the sources.
-	// If zero, a default is used. If negative, the index is only
-	// built once.
-	IndexInterval time.Duration
-
-	// IndexDocs enables indexing of Go documentation.
-	// This will produce search results for exported types, functions,
-	// methods, variables, and constants, and will link to the godoc
-	// documentation for those identifiers.
-	IndexDocs bool
-
-	// IndexGoCode enables indexing of Go source code.
-	// This will produce search results for internal and external identifiers
-	// and will link to both declarations and uses of those identifiers in
-	// source code.
-	IndexGoCode bool
-
-	// IndexFullText enables full-text indexing.
-	// This will provide search results for any matching text in any file that
-	// is indexed, including non-Go files (see whitelisted in index.go).
-	// Regexp searching is supported via full-text indexing.
-	IndexFullText bool
-
-	// MaxResults optionally specifies the maximum results for indexing.
-	MaxResults int
-
-	// SummarizePackage optionally specifies a function to
-	// summarize a package. It exists as an optimization to
-	// avoid reading files to parse package comments.
-	//
-	// If SummarizePackage returns false for ok, the caller
-	// ignores all return values and parses the files in the package
-	// as if SummarizePackage were nil.
-	//
-	// If showList is false, the package is hidden from the
-	// package listing.
-	SummarizePackage func(pkg string) (summary string, showList, ok bool)
-
-	// IndexDirectory optionally specifies a function to determine
-	// whether the provided directory should be indexed.  The dir
-	// will be of the form "/src/cmd/6a", "/doc/play",
-	// "/src/io", etc.
-	// If nil, all directories are indexed if indexing is enabled.
-	IndexDirectory func(dir string) bool
-
-	testDir string // TODO(bradfitz,adg): migrate old godoc flag? looks unused.
-
-	// Send a value on this channel to trigger a metadata refresh.
-	// It is buffered so that if a signal is not lost if sent
-	// during a refresh.
-	refreshMetadataSignal chan bool
-
-	// file system information
-	fsTree      util.RWValue // *Directory tree of packages, updated with each sync (but sync code is removed now)
-	fsModified  util.RWValue // timestamp of last call to invalidateIndex
-	docMetadata util.RWValue // mapping from paths to *Metadata
-
-	// SearchIndex is the search index in use.
-	searchIndex util.RWValue
-
-	// Analysis is the result of type and pointer analysis.
-	Analysis analysis.Result
-
-	// flag to check whether a corpus is initialized or not
-	initMu   sync.RWMutex
-	initDone bool
-
-	// pkgAPIInfo contains the information about which package API
-	// features were added in which version of Go.
-	pkgAPIInfo apiVersions
-}
-
-// NewCorpus returns a new Corpus from a filesystem.
-// The returned corpus has all indexing enabled and MaxResults set to 1000.
-// Change or set any options on Corpus before calling the Corpus.Init method.
-func NewCorpus(fs vfs.FileSystem) *Corpus {
-	c := &Corpus{
-		fs:                    fs,
-		refreshMetadataSignal: make(chan bool, 1),
-
-		MaxResults:    1000,
-		IndexEnabled:  true,
-		IndexDocs:     true,
-		IndexGoCode:   true,
-		IndexFullText: true,
-	}
-	return c
-}
-
-func (c *Corpus) CurrentIndex() (*Index, time.Time) {
-	v, t := c.searchIndex.Get()
-	idx, _ := v.(*Index)
-	return idx, t
-}
-
-func (c *Corpus) FSModifiedTime() time.Time {
-	_, ts := c.fsModified.Get()
-	return ts
-}
-
-// Init initializes Corpus, once options on Corpus are set.
-// It must be called before any subsequent method calls.
-func (c *Corpus) Init() error {
-	if err := c.initFSTree(); err != nil {
-		return err
-	}
-	c.updateMetadata()
-	go c.refreshMetadataLoop()
-
-	c.initMu.Lock()
-	c.initDone = true
-	c.initMu.Unlock()
-	return nil
-}
-
-func (c *Corpus) initFSTree() error {
-	dir := c.newDirectory(pathpkg.Join("/", c.testDir), -1)
-	if dir == nil {
-		return errors.New("godoc: corpus fstree is nil")
-	}
-	c.fsTree.Set(dir)
-	c.invalidateIndex()
-	return nil
-}
diff --git a/cmd/golangorg/godoc/dirtrees.go b/cmd/golangorg/godoc/dirtrees.go
deleted file mode 100644
index 0542c98..0000000
--- a/cmd/golangorg/godoc/dirtrees.go
+++ /dev/null
@@ -1,383 +0,0 @@
-// Copyright 2010 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 contains the code dealing with package directory trees.
-
-package godoc
-
-import (
-	"go/doc"
-	"go/parser"
-	"go/token"
-	"log"
-	"os"
-	pathpkg "path"
-	"runtime"
-	"sort"
-	"strings"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// Conventional name for directories containing test data.
-// Excluded from directory trees.
-//
-const testdataDirName = "testdata"
-
-type Directory struct {
-	Depth    int
-	Path     string       // directory path; includes Name
-	Name     string       // directory name
-	HasPkg   bool         // true if the directory contains at least one package
-	Synopsis string       // package documentation, if any
-	RootType vfs.RootType // root type of the filesystem containing the directory
-	Dirs     []*Directory // subdirectories
-}
-
-func isGoFile(fi os.FileInfo) bool {
-	name := fi.Name()
-	return !fi.IsDir() &&
-		len(name) > 0 && name[0] != '.' && // ignore .files
-		pathpkg.Ext(name) == ".go"
-}
-
-func isPkgFile(fi os.FileInfo) bool {
-	return isGoFile(fi) &&
-		!strings.HasSuffix(fi.Name(), "_test.go") // ignore test files
-}
-
-func isPkgDir(fi os.FileInfo) bool {
-	name := fi.Name()
-	return fi.IsDir() && len(name) > 0 &&
-		name[0] != '_' && name[0] != '.' // ignore _files and .files
-}
-
-type treeBuilder struct {
-	c        *Corpus
-	maxDepth int
-}
-
-// ioGate is a semaphore controlling VFS activity (ReadDir, parseFile, etc).
-// Send before an operation and receive after.
-var ioGate = make(chan struct{}, 20)
-
-// workGate controls the number of concurrent workers. Too many concurrent
-// workers and performance degrades and the race detector gets overwhelmed. If
-// we cannot check out a concurrent worker, work is performed by the main thread
-// instead of spinning up another goroutine.
-var workGate = make(chan struct{}, runtime.NumCPU()*4)
-
-func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth int) *Directory {
-	if name == testdataDirName {
-		return nil
-	}
-
-	if depth >= b.maxDepth {
-		// return a dummy directory so that the parent directory
-		// doesn't get discarded just because we reached the max
-		// directory depth
-		return &Directory{
-			Depth: depth,
-			Path:  path,
-			Name:  name,
-		}
-	}
-
-	var synopses [3]string // prioritized package documentation (0 == highest priority)
-
-	show := true // show in package listing
-	hasPkgFiles := false
-	haveSummary := false
-
-	if hook := b.c.SummarizePackage; hook != nil {
-		if summary, show0, ok := hook(strings.TrimPrefix(path, "/src/")); ok {
-			hasPkgFiles = true
-			show = show0
-			synopses[0] = summary
-			haveSummary = true
-		}
-	}
-
-	ioGate <- struct{}{}
-	list, err := b.c.fs.ReadDir(path)
-	<-ioGate
-	if err != nil {
-		// TODO: propagate more. See golang.org/issue/14252.
-		// For now:
-		if b.c.Verbose {
-			log.Printf("newDirTree reading %s: %v", path, err)
-		}
-	}
-
-	// determine number of subdirectories and if there are package files
-	var dirchs []chan *Directory
-	var dirs []*Directory
-
-	for _, d := range list {
-		filename := pathpkg.Join(path, d.Name())
-		switch {
-		case isPkgDir(d):
-			name := d.Name()
-			select {
-			case workGate <- struct{}{}:
-				ch := make(chan *Directory, 1)
-				dirchs = append(dirchs, ch)
-				go func() {
-					ch <- b.newDirTree(fset, filename, name, depth+1)
-					<-workGate
-				}()
-			default:
-				// no free workers, do work synchronously
-				dir := b.newDirTree(fset, filename, name, depth+1)
-				if dir != nil {
-					dirs = append(dirs, dir)
-				}
-			}
-		case !haveSummary && isPkgFile(d):
-			// looks like a package file, but may just be a file ending in ".go";
-			// don't just count it yet (otherwise we may end up with hasPkgFiles even
-			// though the directory doesn't contain any real package files - was bug)
-			// no "optimal" package synopsis yet; continue to collect synopses
-			ioGate <- struct{}{}
-			const flags = parser.ParseComments | parser.PackageClauseOnly
-			file, err := b.c.parseFile(fset, filename, flags)
-			<-ioGate
-			if err != nil {
-				if b.c.Verbose {
-					log.Printf("Error parsing %v: %v", filename, err)
-				}
-				break
-			}
-
-			hasPkgFiles = true
-			if file.Doc != nil {
-				// prioritize documentation
-				i := -1
-				switch file.Name.Name {
-				case name:
-					i = 0 // normal case: directory name matches package name
-				case "main":
-					i = 1 // directory contains a main package
-				default:
-					i = 2 // none of the above
-				}
-				if 0 <= i && i < len(synopses) && synopses[i] == "" {
-					synopses[i] = doc.Synopsis(file.Doc.Text())
-				}
-			}
-			haveSummary = synopses[0] != ""
-		}
-	}
-
-	// create subdirectory tree
-	for _, ch := range dirchs {
-		if d := <-ch; d != nil {
-			dirs = append(dirs, d)
-		}
-	}
-
-	// We need to sort the dirs slice because
-	// it is appended again after reading from dirchs.
-	sort.Slice(dirs, func(i, j int) bool {
-		return dirs[i].Name < dirs[j].Name
-	})
-
-	// if there are no package files and no subdirectories
-	// containing package files, ignore the directory
-	if !hasPkgFiles && len(dirs) == 0 {
-		return nil
-	}
-
-	// select the highest-priority synopsis for the directory entry, if any
-	synopsis := ""
-	for _, synopsis = range synopses {
-		if synopsis != "" {
-			break
-		}
-	}
-
-	return &Directory{
-		Depth:    depth,
-		Path:     path,
-		Name:     name,
-		HasPkg:   hasPkgFiles && show, // TODO(bradfitz): add proper Hide field?
-		Synopsis: synopsis,
-		RootType: b.c.fs.RootType(path),
-		Dirs:     dirs,
-	}
-}
-
-// newDirectory creates a new package directory tree with at most maxDepth
-// levels, anchored at root. The result tree is pruned such that it only
-// contains directories that contain package files or that contain
-// subdirectories containing package files (transitively). If a non-nil
-// pathFilter is provided, directory paths additionally must be accepted
-// by the filter (i.e., pathFilter(path) must be true). If a value >= 0 is
-// provided for maxDepth, nodes at larger depths are pruned as well; they
-// are assumed to contain package files even if their contents are not known
-// (i.e., in this case the tree may contain directories w/o any package files).
-//
-func (c *Corpus) newDirectory(root string, maxDepth int) *Directory {
-	// The root could be a symbolic link so use Stat not Lstat.
-	d, err := c.fs.Stat(root)
-	// If we fail here, report detailed error messages; otherwise
-	// is is hard to see why a directory tree was not built.
-	switch {
-	case err != nil:
-		log.Printf("newDirectory(%s): %s", root, err)
-		return nil
-	case root != "/" && !isPkgDir(d):
-		log.Printf("newDirectory(%s): not a package directory", root)
-		return nil
-	case root == "/" && !d.IsDir():
-		log.Printf("newDirectory(%s): not a directory", root)
-		return nil
-	}
-	if maxDepth < 0 {
-		maxDepth = 1e6 // "infinity"
-	}
-	b := treeBuilder{c, maxDepth}
-	// the file set provided is only for local parsing, no position
-	// information escapes and thus we don't need to save the set
-	return b.newDirTree(token.NewFileSet(), root, d.Name(), 0)
-}
-
-func (dir *Directory) walk(c chan<- *Directory, skipRoot bool) {
-	if dir != nil {
-		if !skipRoot {
-			c <- dir
-		}
-		for _, d := range dir.Dirs {
-			d.walk(c, false)
-		}
-	}
-}
-
-func (dir *Directory) iter(skipRoot bool) <-chan *Directory {
-	c := make(chan *Directory)
-	go func() {
-		dir.walk(c, skipRoot)
-		close(c)
-	}()
-	return c
-}
-
-func (dir *Directory) lookupLocal(name string) *Directory {
-	for _, d := range dir.Dirs {
-		if d.Name == name {
-			return d
-		}
-	}
-	return nil
-}
-
-func splitPath(p string) []string {
-	p = strings.TrimPrefix(p, "/")
-	if p == "" {
-		return nil
-	}
-	return strings.Split(p, "/")
-}
-
-// lookup looks for the *Directory for a given path, relative to dir.
-func (dir *Directory) lookup(path string) *Directory {
-	d := splitPath(dir.Path)
-	p := splitPath(path)
-	i := 0
-	for i < len(d) {
-		if i >= len(p) || d[i] != p[i] {
-			return nil
-		}
-		i++
-	}
-	for dir != nil && i < len(p) {
-		dir = dir.lookupLocal(p[i])
-		i++
-	}
-	return dir
-}
-
-// DirEntry describes a directory entry. The Depth and Height values
-// are useful for presenting an entry in an indented fashion.
-//
-type DirEntry struct {
-	Depth    int          // >= 0
-	Height   int          // = DirList.MaxHeight - Depth, > 0
-	Path     string       // directory path; includes Name, relative to DirList root
-	Name     string       // directory name
-	HasPkg   bool         // true if the directory contains at least one package
-	Synopsis string       // package documentation, if any
-	RootType vfs.RootType // root type of the filesystem containing the direntry
-}
-
-type DirList struct {
-	MaxHeight int // directory tree height, > 0
-	List      []DirEntry
-}
-
-// hasThirdParty checks whether a list of directory entries has packages outside
-// the standard library or not.
-func hasThirdParty(list []DirEntry) bool {
-	for _, entry := range list {
-		if entry.RootType == vfs.RootTypeGoPath {
-			return true
-		}
-	}
-	return false
-}
-
-// listing creates a (linear) directory listing from a directory tree.
-// If skipRoot is set, the root directory itself is excluded from the list.
-// If filter is set, only the directory entries whose paths match the filter
-// are included.
-//
-func (root *Directory) listing(skipRoot bool, filter func(string) bool) *DirList {
-	if root == nil {
-		return nil
-	}
-
-	// determine number of entries n and maximum height
-	n := 0
-	minDepth := 1 << 30 // infinity
-	maxDepth := 0
-	for d := range root.iter(skipRoot) {
-		n++
-		if minDepth > d.Depth {
-			minDepth = d.Depth
-		}
-		if maxDepth < d.Depth {
-			maxDepth = d.Depth
-		}
-	}
-	maxHeight := maxDepth - minDepth + 1
-
-	if n == 0 {
-		return nil
-	}
-
-	// create list
-	list := make([]DirEntry, 0, n)
-	for d := range root.iter(skipRoot) {
-		if filter != nil && !filter(d.Path) {
-			continue
-		}
-		var p DirEntry
-		p.Depth = d.Depth - minDepth
-		p.Height = maxHeight - p.Depth
-		// the path is relative to root.Path - remove the root.Path
-		// prefix (the prefix should always be present but avoid
-		// crashes and check)
-		path := strings.TrimPrefix(d.Path, root.Path)
-		// remove leading separator if any - path must be relative
-		path = strings.TrimPrefix(path, "/")
-		p.Path = path
-		p.Name = d.Name
-		p.HasPkg = d.HasPkg
-		p.Synopsis = d.Synopsis
-		p.RootType = d.RootType
-		list = append(list, p)
-	}
-
-	return &DirList{maxHeight, list}
-}
diff --git a/cmd/golangorg/godoc/dirtrees_test.go b/cmd/golangorg/godoc/dirtrees_test.go
deleted file mode 100644
index ac5f2a9..0000000
--- a/cmd/golangorg/godoc/dirtrees_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2018 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.
-
-package godoc
-
-import (
-	"go/build"
-	"path/filepath"
-	"runtime"
-	"sort"
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/gatefs"
-)
-
-func TestNewDirTree(t *testing.T) {
-	fsGate := make(chan bool, 20)
-	rootfs := gatefs.New(vfs.OS(runtime.GOROOT()), fsGate)
-	fs := vfs.NameSpace{}
-	fs.Bind("/", rootfs, "/", vfs.BindReplace)
-
-	c := NewCorpus(fs)
-	// 3 levels deep is enough for testing
-	dir := c.newDirectory("/", 3)
-
-	processDir(t, dir)
-}
-
-func processDir(t *testing.T, dir *Directory) {
-	var list []string
-	for _, d := range dir.Dirs {
-		list = append(list, d.Name)
-		// recursively process the lower level
-		processDir(t, d)
-	}
-
-	if sort.StringsAreSorted(list) == false {
-		t.Errorf("list: %v is not sorted\n", list)
-	}
-}
-
-func BenchmarkNewDirectory(b *testing.B) {
-	if testing.Short() {
-		b.Skip("not running tests requiring large file scan in short mode")
-	}
-
-	fsGate := make(chan bool, 20)
-
-	goroot := runtime.GOROOT()
-	rootfs := gatefs.New(vfs.OS(goroot), fsGate)
-	fs := vfs.NameSpace{}
-	fs.Bind("/", rootfs, "/", vfs.BindReplace)
-	for _, p := range filepath.SplitList(build.Default.GOPATH) {
-		fs.Bind("/src/golang.org", gatefs.New(vfs.OS(p), fsGate), "/src/golang.org", vfs.BindAfter)
-	}
-	b.ResetTimer()
-	b.ReportAllocs()
-	for tries := 0; tries < b.N; tries++ {
-		corpus := NewCorpus(fs)
-		corpus.newDirectory("/", -1)
-	}
-}
diff --git a/cmd/golangorg/godoc/dl/dl.go b/cmd/golangorg/godoc/dl/dl.go
deleted file mode 100644
index 41c0f32..0000000
--- a/cmd/golangorg/godoc/dl/dl.go
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-// Package dl implements a simple downloads frontend server.
-//
-// It accepts HTTP POST requests to create a new download metadata entity, and
-// lists entities with sorting and filtering.
-// It is designed to run only on the instance of godoc that serves golang.org.
-package dl
-
-import (
-	"fmt"
-	"html/template"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-)
-
-const (
-	downloadBaseURL = "https://dl.google.com/go/"
-	cacheKey        = "download_list_3" // increment if listTemplateData changes
-	cacheDuration   = time.Hour
-)
-
-// File represents a file on the golang.org downloads page.
-// It should be kept in sync with the upload code in x/build/cmd/release.
-type File struct {
-	Filename       string    `json:"filename"`
-	OS             string    `json:"os"`
-	Arch           string    `json:"arch"`
-	Version        string    `json:"version"`
-	Checksum       string    `json:"-" datastore:",noindex"` // SHA1; deprecated
-	ChecksumSHA256 string    `json:"sha256" datastore:",noindex"`
-	Size           int64     `json:"size" datastore:",noindex"`
-	Kind           string    `json:"kind"` // "archive", "installer", "source"
-	Uploaded       time.Time `json:"-"`
-}
-
-func (f File) ChecksumType() string {
-	if f.ChecksumSHA256 != "" {
-		return "SHA256"
-	}
-	return "SHA1"
-}
-
-func (f File) PrettyChecksum() string {
-	if f.ChecksumSHA256 != "" {
-		return f.ChecksumSHA256
-	}
-	return f.Checksum
-}
-
-func (f File) PrettyOS() string {
-	if f.OS == "darwin" {
-		switch {
-		case strings.Contains(f.Filename, "osx10.8"):
-			return "OS X 10.8+"
-		case strings.Contains(f.Filename, "osx10.6"):
-			return "OS X 10.6+"
-		}
-	}
-	return pretty(f.OS)
-}
-
-func (f File) PrettySize() string {
-	const mb = 1 << 20
-	if f.Size == 0 {
-		return ""
-	}
-	if f.Size < mb {
-		// All Go releases are >1mb, but handle this case anyway.
-		return fmt.Sprintf("%v bytes", f.Size)
-	}
-	return fmt.Sprintf("%.0fMB", float64(f.Size)/mb)
-}
-
-var primaryPorts = map[string]bool{
-	"darwin/amd64":  true,
-	"linux/386":     true,
-	"linux/amd64":   true,
-	"linux/armv6l":  true,
-	"windows/386":   true,
-	"windows/amd64": true,
-}
-
-func (f File) PrimaryPort() bool {
-	if f.Kind == "source" {
-		return true
-	}
-	return primaryPorts[f.OS+"/"+f.Arch]
-}
-
-func (f File) Highlight() bool {
-	switch {
-	case f.Kind == "source":
-		return true
-	case f.Arch == "amd64" && f.OS == "linux":
-		return true
-	case f.Arch == "amd64" && f.Kind == "installer":
-		switch f.OS {
-		case "windows":
-			return true
-		case "darwin":
-			if !strings.Contains(f.Filename, "osx10.6") {
-				return true
-			}
-		}
-	}
-	return false
-}
-
-func (f File) URL() string {
-	return downloadBaseURL + f.Filename
-}
-
-type Release struct {
-	Version        string `json:"version"`
-	Stable         bool   `json:"stable"`
-	Files          []File `json:"files"`
-	Visible        bool   `json:"-"` // show files on page load
-	SplitPortTable bool   `json:"-"` // whether files should be split by primary/other ports.
-}
-
-type Feature struct {
-	// The File field will be filled in by the first stable File
-	// whose name matches the given fileRE.
-	File
-	fileRE *regexp.Regexp
-
-	Platform     string // "Microsoft Windows", "Apple macOS", "Linux"
-	Requirements string // "Windows XP and above, 64-bit Intel Processor"
-}
-
-// featuredFiles lists the platforms and files to be featured
-// at the top of the downloads page.
-var featuredFiles = []Feature{
-	{
-		Platform:     "Microsoft Windows",
-		Requirements: "Windows 7 or later, Intel 64-bit processor",
-		fileRE:       regexp.MustCompile(`\.windows-amd64\.msi$`),
-	},
-	{
-		Platform:     "Apple macOS",
-		Requirements: "macOS 10.10 or later, Intel 64-bit processor",
-		fileRE:       regexp.MustCompile(`\.darwin-amd64(-osx10\.8)?\.pkg$`),
-	},
-	{
-		Platform:     "Linux",
-		Requirements: "Linux 2.6.23 or later, Intel 64-bit processor",
-		fileRE:       regexp.MustCompile(`\.linux-amd64\.tar\.gz$`),
-	},
-	{
-		Platform: "Source",
-		fileRE:   regexp.MustCompile(`\.src\.tar\.gz$`),
-	},
-}
-
-// data to send to the template; increment cacheKey if you change this.
-type listTemplateData struct {
-	Featured                  []Feature
-	Stable, Unstable, Archive []Release
-}
-
-var (
-	listTemplate  = template.Must(template.New("").Funcs(templateFuncs).Parse(templateHTML))
-	templateFuncs = template.FuncMap{"pretty": pretty}
-)
-
-func filesToFeatured(fs []File) (featured []Feature) {
-	for _, feature := range featuredFiles {
-		for _, file := range fs {
-			if feature.fileRE.MatchString(file.Filename) {
-				feature.File = file
-				featured = append(featured, feature)
-				break
-			}
-		}
-	}
-	return
-}
-
-func filesToReleases(fs []File) (stable, unstable, archive []Release) {
-	sort.Sort(fileOrder(fs))
-
-	var r *Release
-	var stableMaj, stableMin int
-	add := func() {
-		if r == nil {
-			return
-		}
-		if !r.Stable {
-			if len(unstable) != 0 {
-				// Only show one (latest) unstable version.
-				return
-			}
-			maj, min, _ := parseVersion(r.Version)
-			if maj < stableMaj || maj == stableMaj && min <= stableMin {
-				// Display unstable version only if newer than the
-				// latest stable release.
-				return
-			}
-			unstable = append(unstable, *r)
-		}
-
-		// Reports whether the release is the most recent minor version of the
-		// two most recent major versions.
-		shouldAddStable := func() bool {
-			if len(stable) >= 2 {
-				// Show up to two stable versions.
-				return false
-			}
-			if len(stable) == 0 {
-				// Most recent stable version.
-				stableMaj, stableMin, _ = parseVersion(r.Version)
-				return true
-			}
-			if maj, _, _ := parseVersion(r.Version); maj == stableMaj {
-				// Older minor version of most recent major version.
-				return false
-			}
-			// Second most recent stable version.
-			return true
-		}
-		if !shouldAddStable() {
-			archive = append(archive, *r)
-			return
-		}
-
-		// Split the file list into primary/other ports for the stable releases.
-		// NOTE(cbro): This is only done for stable releases because maintaining the historical
-		// nature of primary/other ports for older versions is infeasible.
-		// If freebsd is considered primary some time in the future, we'd not want to
-		// mark all of the older freebsd binaries as "primary".
-		// It might be better if we set that as a flag when uploading.
-		r.SplitPortTable = true
-		r.Visible = true // Toggle open all stable releases.
-		stable = append(stable, *r)
-	}
-	for _, f := range fs {
-		if r == nil || f.Version != r.Version {
-			add()
-			r = &Release{
-				Version: f.Version,
-				Stable:  isStable(f.Version),
-			}
-		}
-		r.Files = append(r.Files, f)
-	}
-	add()
-	return
-}
-
-// isStable reports whether the version string v is a stable version.
-func isStable(v string) bool {
-	return !strings.Contains(v, "beta") && !strings.Contains(v, "rc")
-}
-
-type fileOrder []File
-
-func (s fileOrder) Len() int      { return len(s) }
-func (s fileOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-func (s fileOrder) Less(i, j int) bool {
-	a, b := s[i], s[j]
-	if av, bv := a.Version, b.Version; av != bv {
-		return versionLess(av, bv)
-	}
-	if a.OS != b.OS {
-		return a.OS < b.OS
-	}
-	if a.Arch != b.Arch {
-		return a.Arch < b.Arch
-	}
-	if a.Kind != b.Kind {
-		return a.Kind < b.Kind
-	}
-	return a.Filename < b.Filename
-}
-
-func versionLess(a, b string) bool {
-	// Put stable releases first.
-	if isStable(a) != isStable(b) {
-		return isStable(a)
-	}
-	maja, mina, ta := parseVersion(a)
-	majb, minb, tb := parseVersion(b)
-	if maja == majb {
-		if mina == minb {
-			return ta >= tb
-		}
-		return mina >= minb
-	}
-	return maja >= majb
-}
-
-func parseVersion(v string) (maj, min int, tail string) {
-	if i := strings.Index(v, "beta"); i > 0 {
-		tail = v[i:]
-		v = v[:i]
-	}
-	if i := strings.Index(v, "rc"); i > 0 {
-		tail = v[i:]
-		v = v[:i]
-	}
-	p := strings.Split(strings.TrimPrefix(v, "go1."), ".")
-	maj, _ = strconv.Atoi(p[0])
-	if len(p) < 2 {
-		return
-	}
-	min, _ = strconv.Atoi(p[1])
-	return
-}
-
-func validUser(user string) bool {
-	switch user {
-	case "adg", "bradfitz", "cbro", "andybons", "valsorda", "dmitshur", "katiehockman":
-		return true
-	}
-	return false
-}
-
-var (
-	fileRe  = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+\.(tar\.gz|pkg|msi|zip)$`)
-	goGetRe = regexp.MustCompile(`^go[0-9a-z.]+\.[0-9a-z.-]+$`)
-)
-
-// pretty returns a human-readable version of the given OS, Arch, or Kind.
-func pretty(s string) string {
-	t, ok := prettyStrings[s]
-	if !ok {
-		return s
-	}
-	return t
-}
-
-var prettyStrings = map[string]string{
-	"darwin":  "macOS",
-	"freebsd": "FreeBSD",
-	"linux":   "Linux",
-	"windows": "Windows",
-
-	"386":    "x86",
-	"amd64":  "x86-64",
-	"armv6l": "ARMv6",
-	"arm64":  "ARMv8",
-
-	"archive":   "Archive",
-	"installer": "Installer",
-	"source":    "Source",
-}
diff --git a/cmd/golangorg/godoc/dl/dl_test.go b/cmd/golangorg/godoc/dl/dl_test.go
deleted file mode 100644
index 2cdc1aa..0000000
--- a/cmd/golangorg/godoc/dl/dl_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-package dl
-
-import (
-	"sort"
-	"strings"
-	"testing"
-)
-
-func TestParseVersion(t *testing.T) {
-	for _, c := range []struct {
-		in       string
-		maj, min int
-		tail     string
-	}{
-		{"go1.5", 5, 0, ""},
-		{"go1.5beta1", 5, 0, "beta1"},
-		{"go1.5.1", 5, 1, ""},
-		{"go1.5.1rc1", 5, 1, "rc1"},
-	} {
-		maj, min, tail := parseVersion(c.in)
-		if maj != c.maj || min != c.min || tail != c.tail {
-			t.Errorf("parseVersion(%q) = %v, %v, %q; want %v, %v, %q",
-				c.in, maj, min, tail, c.maj, c.min, c.tail)
-		}
-	}
-}
-
-func TestFileOrder(t *testing.T) {
-	fs := []File{
-		{Filename: "go1.3.src.tar.gz", Version: "go1.3", OS: "", Arch: "", Kind: "source"},
-		{Filename: "go1.3.1.src.tar.gz", Version: "go1.3.1", OS: "", Arch: "", Kind: "source"},
-		{Filename: "go1.3.linux-amd64.tar.gz", Version: "go1.3", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.3.1.linux-amd64.tar.gz", Version: "go1.3.1", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.3.darwin-amd64.tar.gz", Version: "go1.3", OS: "darwin", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.3.darwin-amd64.pkg", Version: "go1.3", OS: "darwin", Arch: "amd64", Kind: "installer"},
-		{Filename: "go1.3.darwin-386.tar.gz", Version: "go1.3", OS: "darwin", Arch: "386", Kind: "archive"},
-		{Filename: "go1.3beta1.linux-amd64.tar.gz", Version: "go1.3beta1", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.3beta2.linux-amd64.tar.gz", Version: "go1.3beta2", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.3rc1.linux-amd64.tar.gz", Version: "go1.3rc1", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.2.linux-amd64.tar.gz", Version: "go1.2", OS: "linux", Arch: "amd64", Kind: "archive"},
-		{Filename: "go1.2.2.linux-amd64.tar.gz", Version: "go1.2.2", OS: "linux", Arch: "amd64", Kind: "archive"},
-	}
-	sort.Sort(fileOrder(fs))
-	var s []string
-	for _, f := range fs {
-		s = append(s, f.Filename)
-	}
-	got := strings.Join(s, "\n")
-	want := strings.Join([]string{
-		"go1.3.1.src.tar.gz",
-		"go1.3.1.linux-amd64.tar.gz",
-		"go1.3.src.tar.gz",
-		"go1.3.darwin-386.tar.gz",
-		"go1.3.darwin-amd64.tar.gz",
-		"go1.3.darwin-amd64.pkg",
-		"go1.3.linux-amd64.tar.gz",
-		"go1.2.2.linux-amd64.tar.gz",
-		"go1.2.linux-amd64.tar.gz",
-		"go1.3rc1.linux-amd64.tar.gz",
-		"go1.3beta2.linux-amd64.tar.gz",
-		"go1.3beta1.linux-amd64.tar.gz",
-	}, "\n")
-	if got != want {
-		t.Errorf("sort order is\n%s\nwant:\n%s", got, want)
-	}
-}
-
-func TestFilesToReleases(t *testing.T) {
-	fs := []File{
-		{Version: "go1.7.4", OS: "darwin"},
-		{Version: "go1.7.4", OS: "windows"},
-		{Version: "go1.7", OS: "darwin"},
-		{Version: "go1.7", OS: "windows"},
-		{Version: "go1.6.2", OS: "darwin"},
-		{Version: "go1.6.2", OS: "windows"},
-		{Version: "go1.6", OS: "darwin"},
-		{Version: "go1.6", OS: "windows"},
-		{Version: "go1.5.2", OS: "darwin"},
-		{Version: "go1.5.2", OS: "windows"},
-		{Version: "go1.5", OS: "darwin"},
-		{Version: "go1.5", OS: "windows"},
-		{Version: "go1.5beta1", OS: "windows"},
-	}
-	stable, unstable, archive := filesToReleases(fs)
-	if got, want := len(stable), 2; want != got {
-		t.Errorf("len(stable): got %v, want %v", got, want)
-	} else {
-		if got, want := stable[0].Version, "go1.7.4"; want != got {
-			t.Errorf("stable[0].Version: got %v, want %v", got, want)
-		}
-		if got, want := stable[1].Version, "go1.6.2"; want != got {
-			t.Errorf("stable[1].Version: got %v, want %v", got, want)
-		}
-	}
-	if got, want := len(unstable), 0; want != got {
-		t.Errorf("len(unstable): got %v, want %v", got, want)
-	}
-	if got, want := len(archive), 4; want != got {
-		t.Errorf("len(archive): got %v, want %v", got, want)
-	}
-}
-
-func TestOldUnstableNotShown(t *testing.T) {
-	fs := []File{
-		{Version: "go1.7.4"},
-		{Version: "go1.7"},
-		{Version: "go1.7beta1"},
-	}
-	_, unstable, _ := filesToReleases(fs)
-	if len(unstable) != 0 {
-		t.Errorf("got unstable, want none")
-	}
-}
-
-func TestUnstableShown(t *testing.T) {
-	fs := []File{
-		{Version: "go1.8beta2"},
-		{Version: "go1.8rc1"},
-		{Version: "go1.7.4"},
-		{Version: "go1.7"},
-		{Version: "go1.7beta1"},
-	}
-	_, unstable, _ := filesToReleases(fs)
-	if got, want := len(unstable), 1; got != want {
-		t.Fatalf("len(unstable): got %v, want %v", got, want)
-	}
-	// show rcs ahead of betas.
-	if got, want := unstable[0].Version, "go1.8rc1"; got != want {
-		t.Fatalf("unstable[0].Version: got %v, want %v", got, want)
-	}
-}
diff --git a/cmd/golangorg/godoc/dl/server.go b/cmd/golangorg/godoc/dl/server.go
deleted file mode 100644
index 3dd38e9..0000000
--- a/cmd/golangorg/godoc/dl/server.go
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-// +build golangorg
-
-package dl
-
-import (
-	"context"
-	"crypto/hmac"
-	"crypto/md5"
-	"encoding/json"
-	"fmt"
-	"html"
-	"io"
-	"log"
-	"net/http"
-	"strings"
-	"sync"
-	"time"
-
-	"cloud.google.com/go/datastore"
-	"golang.org/x/website/cmd/golangorg/godoc/env"
-	"golang.org/x/website/internal/memcache"
-)
-
-type server struct {
-	datastore *datastore.Client
-	memcache  *memcache.CodecClient
-}
-
-func RegisterHandlers(mux *http.ServeMux, dc *datastore.Client, mc *memcache.Client) {
-	s := server{dc, mc.WithCodec(memcache.Gob)}
-	mux.HandleFunc("/dl", s.getHandler)
-	mux.HandleFunc("/dl/", s.getHandler) // also serves listHandler
-	mux.HandleFunc("/dl/upload", s.uploadHandler)
-
-	// NOTE(cbro): this only needs to be run once per project,
-	// and should be behind an admin login.
-	// TODO(cbro): move into a locally-run program? or remove?
-	// mux.HandleFunc("/dl/init", initHandler)
-}
-
-// rootKey is the ancestor of all File entities.
-var rootKey = datastore.NameKey("FileRoot", "root", nil)
-
-func (h server) listHandler(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "GET" {
-		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
-		return
-	}
-	ctx := r.Context()
-	var d listTemplateData
-
-	if err := h.memcache.Get(ctx, cacheKey, &d); err != nil {
-		if err != memcache.ErrCacheMiss {
-			log.Printf("ERROR cache get error: %v", err)
-			// NOTE(cbro): continue to hit datastore if the memcache is down.
-		}
-
-		var fs []File
-		q := datastore.NewQuery("File").Ancestor(rootKey)
-		if _, err := h.datastore.GetAll(ctx, q, &fs); err != nil {
-			log.Printf("ERROR error listing: %v", err)
-			http.Error(w, "Could not get download page. Try again in a few minutes.", 500)
-			return
-		}
-		d.Stable, d.Unstable, d.Archive = filesToReleases(fs)
-		if len(d.Stable) > 0 {
-			d.Featured = filesToFeatured(d.Stable[0].Files)
-		}
-
-		item := &memcache.Item{Key: cacheKey, Object: &d, Expiration: cacheDuration}
-		if err := h.memcache.Set(ctx, item); err != nil {
-			log.Printf("ERROR cache set error: %v", err)
-		}
-	}
-
-	if r.URL.Query().Get("mode") == "json" {
-		w.Header().Set("Content-Type", "application/json")
-		enc := json.NewEncoder(w)
-		enc.SetIndent("", " ")
-		if err := enc.Encode(d.Stable); err != nil {
-			log.Printf("ERROR rendering JSON for releases: %v", err)
-		}
-		return
-	}
-
-	if err := listTemplate.ExecuteTemplate(w, "root", d); err != nil {
-		log.Printf("ERROR executing template: %v", err)
-	}
-}
-
-func (h server) uploadHandler(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
-		return
-	}
-	ctx := r.Context()
-
-	// Authenticate using a user token (same as gomote).
-	user := r.FormValue("user")
-	if !validUser(user) {
-		http.Error(w, "bad user", http.StatusForbidden)
-		return
-	}
-	if r.FormValue("key") != h.userKey(ctx, user) {
-		http.Error(w, "bad key", http.StatusForbidden)
-		return
-	}
-
-	var f File
-	defer r.Body.Close()
-	if err := json.NewDecoder(r.Body).Decode(&f); err != nil {
-		log.Printf("ERROR decoding upload JSON: %v", err)
-		http.Error(w, "Something broke", http.StatusInternalServerError)
-		return
-	}
-	if f.Filename == "" {
-		http.Error(w, "Must provide Filename", http.StatusBadRequest)
-		return
-	}
-	if f.Uploaded.IsZero() {
-		f.Uploaded = time.Now()
-	}
-	k := datastore.NameKey("File", f.Filename, rootKey)
-	if _, err := h.datastore.Put(ctx, k, &f); err != nil {
-		log.Printf("ERROR File entity: %v", err)
-		http.Error(w, "could not put File entity", http.StatusInternalServerError)
-		return
-	}
-	if err := h.memcache.Delete(ctx, cacheKey); err != nil {
-		log.Printf("ERROR delete error: %v", err)
-	}
-	io.WriteString(w, "OK")
-}
-
-func (h server) getHandler(w http.ResponseWriter, r *http.Request) {
-	isGoGet := (r.Method == "GET" || r.Method == "HEAD") && r.FormValue("go-get") == "1"
-	// For go get, we need to serve the same meta tags at /dl for cmd/go to
-	// validate against the import path.
-	if r.URL.Path == "/dl" && isGoGet {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		fmt.Fprintf(w, `<!DOCTYPE html><html><head>
-<meta name="go-import" content="golang.org/dl git https://go.googlesource.com/dl">
-</head></html>`)
-		return
-	}
-	if r.URL.Path == "/dl" {
-		http.Redirect(w, r, "/dl/", http.StatusFound)
-		return
-	}
-
-	name := strings.TrimPrefix(r.URL.Path, "/dl/")
-	var redirectURL string
-	switch {
-	case name == "":
-		h.listHandler(w, r)
-		return
-	case fileRe.MatchString(name):
-		http.Redirect(w, r, downloadBaseURL+name, http.StatusFound)
-		return
-	case name == "gotip":
-		redirectURL = "https://godoc.org/golang.org/dl/gotip"
-	case goGetRe.MatchString(name):
-		redirectURL = "https://golang.org/dl/#" + name
-	default:
-		http.NotFound(w, r)
-		return
-	}
-	w.Header().Set("Content-Type", "text/html; charset=utf-8")
-	if !isGoGet {
-		w.Header().Set("Location", redirectURL)
-	}
-	fmt.Fprintf(w, `<!DOCTYPE html>
-<html>
-<head>
-<meta name="go-import" content="golang.org/dl git https://go.googlesource.com/dl">
-<meta http-equiv="refresh" content="0; url=%s">
-</head>
-<body>
-Nothing to see here; <a href="%s">move along</a>.
-</body>
-</html>
-`, html.EscapeString(redirectURL), html.EscapeString(redirectURL))
-}
-
-func (h server) initHandler(w http.ResponseWriter, r *http.Request) {
-	var fileRoot struct {
-		Root string
-	}
-	ctx := r.Context()
-	k := rootKey
-	_, err := h.datastore.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
-		err := tx.Get(k, &fileRoot)
-		if err != nil && err != datastore.ErrNoSuchEntity {
-			return err
-		}
-		_, err = tx.Put(k, &fileRoot)
-		return err
-	}, nil)
-	if err != nil {
-		http.Error(w, err.Error(), 500)
-		return
-	}
-	io.WriteString(w, "OK")
-}
-
-func (h server) userKey(c context.Context, user string) string {
-	hash := hmac.New(md5.New, []byte(h.secret(c)))
-	hash.Write([]byte("user-" + user))
-	return fmt.Sprintf("%x", hash.Sum(nil))
-}
-
-// Code below copied from x/build/app/key
-
-var theKey struct {
-	sync.RWMutex
-	builderKey
-}
-
-type builderKey struct {
-	Secret string
-}
-
-func (k *builderKey) Key() *datastore.Key {
-	return datastore.NameKey("BuilderKey", "root", nil)
-}
-
-func (h server) secret(ctx context.Context) string {
-	// check with rlock
-	theKey.RLock()
-	k := theKey.Secret
-	theKey.RUnlock()
-	if k != "" {
-		return k
-	}
-
-	// prepare to fill; check with lock and keep lock
-	theKey.Lock()
-	defer theKey.Unlock()
-	if theKey.Secret != "" {
-		return theKey.Secret
-	}
-
-	// fill
-	if err := h.datastore.Get(ctx, theKey.Key(), &theKey.builderKey); err != nil {
-		if err == datastore.ErrNoSuchEntity {
-			// If the key is not stored in datastore, write it.
-			// This only happens at the beginning of a new deployment.
-			// The code is left here for SDK use and in case a fresh
-			// deployment is ever needed.  "gophers rule" is not the
-			// real key.
-			if env.IsProd() {
-				panic("lost key from datastore")
-			}
-			theKey.Secret = "gophers rule"
-			h.datastore.Put(ctx, theKey.Key(), &theKey.builderKey)
-			return theKey.Secret
-		}
-		panic("cannot load builder key: " + err.Error())
-	}
-
-	return theKey.Secret
-}
diff --git a/cmd/golangorg/godoc/dl/tmpl.go b/cmd/golangorg/godoc/dl/tmpl.go
deleted file mode 100644
index d086b69..0000000
--- a/cmd/golangorg/godoc/dl/tmpl.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-package dl
-
-// TODO(adg): refactor this to use the tools/godoc/static template.
-
-const templateHTML = `
-{{define "root"}}
-<!DOCTYPE html>
-<html>
-<head>
-        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-        <title>Downloads - The Go Programming Language</title>
-        <link type="text/css" rel="stylesheet" href="/lib/godoc/style.css">
-        <script type="text/javascript">window.initFuncs = [];</script>
-	<style>
-		table.codetable {
-			margin-left: 20px; margin-right: 20px;
-			border-collapse: collapse;
-		}
-		table.codetable tr {
-			background-color: #f0f0f0;
-		}
-		table.codetable tr:nth-child(2n), table.codetable tr.first {
-			background-color: white;
-		}
-		table.codetable td, table.codetable th {
-			white-space: nowrap;
-			padding: 6px 10px;
-		}
-		table.codetable tt {
-			font-size: xx-small;
-		}
-		table.codetable tr.highlight td {
-			font-weight: bold;
-		}
-		a.downloadBox {
-			display: block;
-			color: #222;
-			border: 1px solid #375EAB;
-			border-radius: 5px;
-			background: #E0EBF5;
-			width: 280px;
-			float: left;
-			margin-left: 10px;
-			margin-bottom: 10px;
-			padding: 10px;
-		}
-		a.downloadBox:hover {
-			text-decoration: none;
-		}
-		.downloadBox .platform {
-			font-size: large;
-		}
-		.downloadBox .filename {
-			color: #375EAB;
-			font-weight: bold;
-			line-height: 1.5em;
-		}
-		a.downloadBox:hover .filename {
-			text-decoration: underline;
-		}
-		.downloadBox .size {
-			font-size: small;
-			font-weight: normal;
-		}
-		.downloadBox .reqs {
-			font-size: small;
-			font-style: italic;
-		}
-		.downloadBox .checksum {
-			font-size: 5pt;
-		}
-	</style>
-</head>
-<body>
-
-<div id="topbar"><div class="container">
-
-<div class="top-heading"><a href="/">The Go Programming Language</a></div>
-<form method="GET" action="/search">
-<div id="menu">
-<a href="/doc/">Documents</a>
-<a href="/pkg/">Packages</a>
-<a href="/project/">The Project</a>
-<a href="/help/">Help</a>
-<a href="/blog/">Blog</a>
-<span class="search-box"><input type="search" id="search" name="q" placeholder="Search" aria-label="Search" required><button type="submit"><span><!-- magnifying glass: --><svg width="24" height="24" viewBox="0 0 24 24"><title>submit search</title><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg></span></button></span>
-</div>
-</form>
-
-</div></div>
-
-<div id="page">
-<div class="container">
-
-<h1>Downloads</h1>
-
-<p>
-After downloading a binary release suitable for your system,
-please follow the <a href="/doc/install">installation instructions</a>.
-</p>
-
-<p>
-If you are building from source,
-follow the <a href="/doc/install/source">source installation instructions</a>.
-</p>
-
-<p>
-See the <a href="/doc/devel/release.html">release history</a> for more
-information about Go releases.
-</p>
-
-{{with .Featured}}
-<h3 id="featured">Featured downloads</h3>
-{{range .}}
-{{template "download" .}}
-{{end}}
-{{end}}
-
-<div style="clear: both;"></div>
-
-{{with .Stable}}
-<h3 id="stable">Stable versions</h3>
-{{template "releases" .}}
-{{end}}
-
-{{with .Unstable}}
-<h3 id="unstable">Unstable version</h3>
-{{template "releases" .}}
-{{end}}
-
-{{with .Archive}}
-<div class="toggle" id="archive">
-  <div class="collapsed">
-    <h3 class="toggleButton" title="Click to show versions">Archived versions▹</h3>
-  </div>
-  <div class="expanded">
-    <h3 class="toggleButton" title="Click to hide versions">Archived versions▾</h3>
-    {{template "releases" .}}
-  </div>
-</div>
-{{end}}
-
-<div id="footer">
-        <p>
-        Except as
-        <a href="https://developers.google.com/site-policies#restrictions">noted</a>,
-        the content of this page is licensed under the Creative Commons
-        Attribution 3.0 License,<br>
-        and code is licensed under a <a href="http://golang.org/LICENSE">BSD license</a>.<br>
-        <a href="http://golang.org/doc/tos.html">Terms of Service</a> |
-        <a href="http://www.google.com/intl/en/policies/privacy/">Privacy Policy</a>
-        </p>
-</div><!-- #footer -->
-
-</div><!-- .container -->
-</div><!-- #page -->
-<script>
-  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-  ga('create', 'UA-11222381-2', 'auto');
-  ga('send', 'pageview');
-
-</script>
-</body>
-<script src="/lib/godoc/jquery.js"></script>
-<script src="/lib/godoc/godocs.js"></script>
-<script>
-$(document).ready(function() {
-  $('a.download').click(function(e) {
-    // Try using the link text as the file name,
-    // unless there's a child element of class 'filename'.
-    var filename = $(this).text();
-    var child = $(this).find('.filename');
-    if (child.length > 0) {
-      filename = child.text();
-    }
-
-    // This must be kept in sync with the filenameRE in godocs.js.
-    var filenameRE = /^go1\.\d+(\.\d+)?([a-z0-9]+)?\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\.[68])?\.([a-z.]+)$/;
-    var m = filenameRE.exec(filename);
-    if (!m) {
-      // Don't redirect to the download page if it won't recognize this file.
-      // (Should not happen.)
-      return;
-    }
-
-    var dest = "/doc/install";
-    if (filename.indexOf(".src.") != -1) {
-      dest += "/source";
-    }
-    dest += "?download=" + filename;
-
-    e.preventDefault();
-    e.stopPropagation();
-    window.location = dest;
-  });
-});
-</script>
-</html>
-{{end}}
-
-{{define "releases"}}
-{{range .}}
-<div class="toggle{{if .Visible}}Visible{{end}}" id="{{.Version}}">
-	<div class="collapsed">
-		<h2 class="toggleButton" title="Click to show downloads for this version">{{.Version}} ▹</h2>
-	</div>
-	<div class="expanded">
-		<h2 class="toggleButton" title="Click to hide downloads for this version">{{.Version}} ▾</h2>
-		{{if .Stable}}{{else}}
-			<p>This is an <b>unstable</b> version of Go. Use with caution.</p>
-			<p>If you already have Go installed, you can install this version by running:</p>
-<pre>
-go get golang.org/dl/{{.Version}}
-</pre>
-			<p>Then, use the <code>{{.Version}}</code> command instead of the <code>go</code> command to use {{.Version}}.</p>
-		{{end}}
-		{{template "files" .}}
-	</div>
-</div>
-{{end}}
-{{end}}
-
-{{define "files"}}
-<table class="codetable">
-<thead>
-<tr class="first">
-  <th>File name</th>
-  <th>Kind</th>
-  <th>OS</th>
-  <th>Arch</th>
-  <th>Size</th>
-  {{/* Use the checksum type of the first file for the column heading. */}}
-  <th>{{(index .Files 0).ChecksumType}} Checksum</th>
-</tr>
-</thead>
-{{if .SplitPortTable}}
-  {{range .Files}}{{if .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
-
-  {{/* TODO(cbro): add a link to an explanatory doc page */}}
-  <tr class="first"><th colspan="6" class="first">Other Ports</th></tr>
-  {{range .Files}}{{if not .PrimaryPort}}{{template "file" .}}{{end}}{{end}}
-{{else}}
-  {{range .Files}}{{template "file" .}}{{end}}
-{{end}}
-</table>
-{{end}}
-
-{{define "file"}}
-<tr{{if .Highlight}} class="highlight"{{end}}>
-  <td class="filename"><a class="download" href="{{.URL}}">{{.Filename}}</a></td>
-  <td>{{pretty .Kind}}</td>
-  <td>{{.PrettyOS}}</td>
-  <td>{{pretty .Arch}}</td>
-  <td>{{.PrettySize}}</td>
-  <td><tt>{{.PrettyChecksum}}</tt></td>
-</tr>
-{{end}}
-
-{{define "download"}}
-<a class="download downloadBox" href="{{.URL}}">
-<div class="platform">{{.Platform}}</div>
-{{with .Requirements}}<div class="reqs">{{.}}</div>{{end}}
-<div>
-  <span class="filename">{{.Filename}}</span>
-  {{if .Size}}<span class="size">({{.PrettySize}})</span>{{end}}
-</div>
-</a>
-{{end}}
-`
diff --git a/cmd/golangorg/godoc/env/env.go b/cmd/golangorg/godoc/env/env.go
deleted file mode 100644
index e1f55cd..0000000
--- a/cmd/golangorg/godoc/env/env.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 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.
-
-// Package env provides environment information for the godoc server running on
-// golang.org.
-package env
-
-import (
-	"log"
-	"os"
-	"strconv"
-)
-
-var (
-	isProd       = boolEnv("GODOC_PROD")
-	enforceHosts = boolEnv("GODOC_ENFORCE_HOSTS")
-)
-
-// IsProd reports whether the server is running in its production configuration
-// on golang.org.
-func IsProd() bool {
-	return isProd
-}
-
-// EnforceHosts reports whether host filtering should be enforced.
-func EnforceHosts() bool {
-	return enforceHosts
-}
-
-func boolEnv(key string) bool {
-	v := os.Getenv(key)
-	if v == "" {
-		return false
-	}
-	b, err := strconv.ParseBool(v)
-	if err != nil {
-		log.Fatalf("environment variable %s (%q) must be a boolean", key, v)
-	}
-	return b
-}
diff --git a/cmd/golangorg/godoc/format.go b/cmd/golangorg/godoc/format.go
deleted file mode 100644
index 6013238..0000000
--- a/cmd/golangorg/godoc/format.go
+++ /dev/null
@@ -1,371 +0,0 @@
-// Copyright 2011 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 FormatSelections and FormatText.
-// FormatText is used to HTML-format Go and non-Go source
-// text with line numbers and highlighted sections. It is
-// built on top of FormatSelections, a generic formatter
-// for "selected" text.
-
-package godoc
-
-import (
-	"fmt"
-	"go/scanner"
-	"go/token"
-	"io"
-	"regexp"
-	"strconv"
-	"text/template"
-)
-
-// ----------------------------------------------------------------------------
-// Implementation of FormatSelections
-
-// A Segment describes a text segment [start, end).
-// The zero value of a Segment is a ready-to-use empty segment.
-//
-type Segment struct {
-	start, end int
-}
-
-func (seg *Segment) isEmpty() bool { return seg.start >= seg.end }
-
-// A Selection is an "iterator" function returning a text segment.
-// Repeated calls to a selection return consecutive, non-overlapping,
-// non-empty segments, followed by an infinite sequence of empty
-// segments. The first empty segment marks the end of the selection.
-//
-type Selection func() Segment
-
-// A LinkWriter writes some start or end "tag" to w for the text offset offs.
-// It is called by FormatSelections at the start or end of each link segment.
-//
-type LinkWriter func(w io.Writer, offs int, start bool)
-
-// A SegmentWriter formats a text according to selections and writes it to w.
-// The selections parameter is a bit set indicating which selections provided
-// to FormatSelections overlap with the text segment: If the n'th bit is set
-// in selections, the n'th selection provided to FormatSelections is overlapping
-// with the text.
-//
-type SegmentWriter func(w io.Writer, text []byte, selections int)
-
-// FormatSelections takes a text and writes it to w using link and segment
-// writers lw and sw as follows: lw is invoked for consecutive segment starts
-// and ends as specified through the links selection, and sw is invoked for
-// consecutive segments of text overlapped by the same selections as specified
-// by selections. The link writer lw may be nil, in which case the links
-// Selection is ignored.
-//
-func FormatSelections(w io.Writer, text []byte, lw LinkWriter, links Selection, sw SegmentWriter, selections ...Selection) {
-	// If we have a link writer, make the links
-	// selection the last entry in selections
-	if lw != nil {
-		selections = append(selections, links)
-	}
-
-	// compute the sequence of consecutive segment changes
-	changes := newMerger(selections)
-
-	// The i'th bit in bitset indicates that the text
-	// at the current offset is covered by selections[i].
-	bitset := 0
-	lastOffs := 0
-
-	// Text segments are written in a delayed fashion
-	// such that consecutive segments belonging to the
-	// same selection can be combined (peephole optimization).
-	// last describes the last segment which has not yet been written.
-	var last struct {
-		begin, end int // valid if begin < end
-		bitset     int
-	}
-
-	// flush writes the last delayed text segment
-	flush := func() {
-		if last.begin < last.end {
-			sw(w, text[last.begin:last.end], last.bitset)
-		}
-		last.begin = last.end // invalidate last
-	}
-
-	// segment runs the segment [lastOffs, end) with the selection
-	// indicated by bitset through the segment peephole optimizer.
-	segment := func(end int) {
-		if lastOffs < end { // ignore empty segments
-			if last.end != lastOffs || last.bitset != bitset {
-				// the last segment is not adjacent to or
-				// differs from the new one
-				flush()
-				// start a new segment
-				last.begin = lastOffs
-			}
-			last.end = end
-			last.bitset = bitset
-		}
-	}
-
-	for {
-		// get the next segment change
-		index, offs, start := changes.next()
-		if index < 0 || offs > len(text) {
-			// no more segment changes or the next change
-			// is past the end of the text - we're done
-			break
-		}
-		// determine the kind of segment change
-		if lw != nil && index == len(selections)-1 {
-			// we have a link segment change (see start of this function):
-			// format the previous selection segment, write the
-			// link tag and start a new selection segment
-			segment(offs)
-			flush()
-			lastOffs = offs
-			lw(w, offs, start)
-		} else {
-			// we have a selection change:
-			// format the previous selection segment, determine
-			// the new selection bitset and start a new segment
-			segment(offs)
-			lastOffs = offs
-			mask := 1 << uint(index)
-			if start {
-				bitset |= mask
-			} else {
-				bitset &^= mask
-			}
-		}
-	}
-	segment(len(text))
-	flush()
-}
-
-// A merger merges a slice of Selections and produces a sequence of
-// consecutive segment change events through repeated next() calls.
-//
-type merger struct {
-	selections []Selection
-	segments   []Segment // segments[i] is the next segment of selections[i]
-}
-
-const infinity int = 2e9
-
-func newMerger(selections []Selection) *merger {
-	segments := make([]Segment, len(selections))
-	for i, sel := range selections {
-		segments[i] = Segment{infinity, infinity}
-		if sel != nil {
-			if seg := sel(); !seg.isEmpty() {
-				segments[i] = seg
-			}
-		}
-	}
-	return &merger{selections, segments}
-}
-
-// next returns the next segment change: index specifies the Selection
-// to which the segment belongs, offs is the segment start or end offset
-// as determined by the start value. If there are no more segment changes,
-// next returns an index value < 0.
-//
-func (m *merger) next() (index, offs int, start bool) {
-	// find the next smallest offset where a segment starts or ends
-	offs = infinity
-	index = -1
-	for i, seg := range m.segments {
-		switch {
-		case seg.start < offs:
-			offs = seg.start
-			index = i
-			start = true
-		case seg.end < offs:
-			offs = seg.end
-			index = i
-			start = false
-		}
-	}
-	if index < 0 {
-		// no offset found => all selections merged
-		return
-	}
-	// offset found - it's either the start or end offset but
-	// either way it is ok to consume the start offset: set it
-	// to infinity so it won't be considered in the following
-	// next call
-	m.segments[index].start = infinity
-	if start {
-		return
-	}
-	// end offset found - consume it
-	m.segments[index].end = infinity
-	// advance to the next segment for that selection
-	seg := m.selections[index]()
-	if !seg.isEmpty() {
-		m.segments[index] = seg
-	}
-	return
-}
-
-// ----------------------------------------------------------------------------
-// Implementation of FormatText
-
-// lineSelection returns the line segments for text as a Selection.
-func lineSelection(text []byte) Selection {
-	i, j := 0, 0
-	return func() (seg Segment) {
-		// find next newline, if any
-		for j < len(text) {
-			j++
-			if text[j-1] == '\n' {
-				break
-			}
-		}
-		if i < j {
-			// text[i:j] constitutes a line
-			seg = Segment{i, j}
-			i = j
-		}
-		return
-	}
-}
-
-// tokenSelection returns, as a selection, the sequence of
-// consecutive occurrences of token sel in the Go src text.
-//
-func tokenSelection(src []byte, sel token.Token) Selection {
-	var s scanner.Scanner
-	fset := token.NewFileSet()
-	file := fset.AddFile("", fset.Base(), len(src))
-	s.Init(file, src, nil, scanner.ScanComments)
-	return func() (seg Segment) {
-		for {
-			pos, tok, lit := s.Scan()
-			if tok == token.EOF {
-				break
-			}
-			offs := file.Offset(pos)
-			if tok == sel {
-				seg = Segment{offs, offs + len(lit)}
-				break
-			}
-		}
-		return
-	}
-}
-
-// makeSelection is a helper function to make a Selection from a slice of pairs.
-// Pairs describing empty segments are ignored.
-//
-func makeSelection(matches [][]int) Selection {
-	i := 0
-	return func() Segment {
-		for i < len(matches) {
-			m := matches[i]
-			i++
-			if m[0] < m[1] {
-				// non-empty segment
-				return Segment{m[0], m[1]}
-			}
-		}
-		return Segment{}
-	}
-}
-
-// regexpSelection computes the Selection for the regular expression expr in text.
-func regexpSelection(text []byte, expr string) Selection {
-	var matches [][]int
-	if rx, err := regexp.Compile(expr); err == nil {
-		matches = rx.FindAllIndex(text, -1)
-	}
-	return makeSelection(matches)
-}
-
-var selRx = regexp.MustCompile(`^([0-9]+):([0-9]+)`)
-
-// RangeSelection computes the Selection for a text range described
-// by the argument str; the range description must match the selRx
-// regular expression.
-func RangeSelection(str string) Selection {
-	m := selRx.FindStringSubmatch(str)
-	if len(m) >= 2 {
-		from, _ := strconv.Atoi(m[1])
-		to, _ := strconv.Atoi(m[2])
-		if from < to {
-			return makeSelection([][]int{{from, to}})
-		}
-	}
-	return nil
-}
-
-// Span tags for all the possible selection combinations that may
-// be generated by FormatText. Selections are indicated by a bitset,
-// and the value of the bitset specifies the tag to be used.
-//
-// bit 0: comments
-// bit 1: highlights
-// bit 2: selections
-//
-var startTags = [][]byte{
-	/* 000 */ []byte(``),
-	/* 001 */ []byte(`<span class="comment">`),
-	/* 010 */ []byte(`<span class="highlight">`),
-	/* 011 */ []byte(`<span class="highlight-comment">`),
-	/* 100 */ []byte(`<span class="selection">`),
-	/* 101 */ []byte(`<span class="selection-comment">`),
-	/* 110 */ []byte(`<span class="selection-highlight">`),
-	/* 111 */ []byte(`<span class="selection-highlight-comment">`),
-}
-
-var endTag = []byte(`</span>`)
-
-func selectionTag(w io.Writer, text []byte, selections int) {
-	if selections < len(startTags) {
-		if tag := startTags[selections]; len(tag) > 0 {
-			w.Write(tag)
-			template.HTMLEscape(w, text)
-			w.Write(endTag)
-			return
-		}
-	}
-	template.HTMLEscape(w, text)
-}
-
-// FormatText HTML-escapes text and writes it to w.
-// Consecutive text segments are wrapped in HTML spans (with tags as
-// defined by startTags and endTag) as follows:
-//
-//	- if line >= 0, line number (ln) spans are inserted before each line,
-//	  starting with the value of line
-//	- if the text is Go source, comments get the "comment" span class
-//	- each occurrence of the regular expression pattern gets the "highlight"
-//	  span class
-//	- text segments covered by selection get the "selection" span class
-//
-// Comments, highlights, and selections may overlap arbitrarily; the respective
-// HTML span classes are specified in the startTags variable.
-//
-func FormatText(w io.Writer, text []byte, line int, goSource bool, pattern string, selection Selection) {
-	var comments, highlights Selection
-	if goSource {
-		comments = tokenSelection(text, token.COMMENT)
-	}
-	if pattern != "" {
-		highlights = regexpSelection(text, pattern)
-	}
-	if line >= 0 || comments != nil || highlights != nil || selection != nil {
-		var lineTag LinkWriter
-		if line >= 0 {
-			lineTag = func(w io.Writer, _ int, start bool) {
-				if start {
-					fmt.Fprintf(w, "<span id=\"L%d\" class=\"ln\">%6d</span>\t", line, line)
-					line++
-				}
-			}
-		}
-		FormatSelections(w, text, lineTag, lineSelection(text), selectionTag, comments, highlights, selection)
-	} else {
-		template.HTMLEscape(w, text)
-	}
-}
diff --git a/cmd/golangorg/godoc/godoc.go b/cmd/golangorg/godoc/godoc.go
deleted file mode 100644
index 6ed8b3d..0000000
--- a/cmd/golangorg/godoc/godoc.go
+++ /dev/null
@@ -1,959 +0,0 @@
-// 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.
-
-// 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/cmd/golangorg/godoc"
-
-import (
-	"bufio"
-	"bytes"
-	"fmt"
-	"go/ast"
-	"go/doc"
-	"go/format"
-	"go/printer"
-	"go/token"
-	htmltemplate "html/template"
-	"io"
-	"log"
-	"os"
-	pathpkg "path"
-	"regexp"
-	"strconv"
-	"strings"
-	"text/template"
-	"time"
-	"unicode"
-	"unicode/utf8"
-)
-
-// Fake relative package path for built-ins. Documentation for all globals
-// (not just exported ones) will be shown for packages in this directory.
-const builtinPkgPath = "builtin"
-
-// FuncMap defines template functions used in godoc templates.
-//
-// Convention: template function names ending in "_html" or "_url" produce
-//             HTML- or URL-escaped strings; all other function results may
-//             require explicit escaping in the template.
-func (p *Presentation) FuncMap() template.FuncMap {
-	p.initFuncMapOnce.Do(p.initFuncMap)
-	return p.funcMap
-}
-
-func (p *Presentation) TemplateFuncs() template.FuncMap {
-	p.initFuncMapOnce.Do(p.initFuncMap)
-	return p.templateFuncs
-}
-
-func (p *Presentation) initFuncMap() {
-	if p.Corpus == nil {
-		panic("nil Presentation.Corpus")
-	}
-	p.templateFuncs = template.FuncMap{
-		"code": p.code,
-	}
-	p.funcMap = template.FuncMap{
-		// various helpers
-		"filename": filenameFunc,
-		"repeat":   strings.Repeat,
-		"since":    p.Corpus.pkgAPIInfo.sinceVersionFunc,
-
-		// access to FileInfos (directory listings)
-		"fileInfoName": fileInfoNameFunc,
-		"fileInfoTime": fileInfoTimeFunc,
-
-		// access to search result information
-		"infoKind_html":    infoKind_htmlFunc,
-		"infoLine":         p.infoLineFunc,
-		"infoSnippet_html": p.infoSnippet_htmlFunc,
-
-		// formatting of AST nodes
-		"node":         p.nodeFunc,
-		"node_html":    p.node_htmlFunc,
-		"comment_html": comment_htmlFunc,
-		"sanitize":     sanitizeFunc,
-
-		// support for URL attributes
-		"pkgLink":       pkgLinkFunc,
-		"srcLink":       srcLinkFunc,
-		"posLink_url":   newPosLink_urlFunc(srcPosLinkFunc),
-		"docLink":       docLinkFunc,
-		"queryLink":     queryLinkFunc,
-		"srcBreadcrumb": srcBreadcrumbFunc,
-		"srcToPkgLink":  srcToPkgLinkFunc,
-
-		// formatting of Examples
-		"example_html":   p.example_htmlFunc,
-		"example_name":   p.example_nameFunc,
-		"example_suffix": p.example_suffixFunc,
-
-		// formatting of analysis information
-		"callgraph_html":  p.callgraph_htmlFunc,
-		"implements_html": p.implements_htmlFunc,
-		"methodset_html":  p.methodset_htmlFunc,
-
-		// formatting of Notes
-		"noteTitle": noteTitle,
-
-		// Number operation
-		"multiply": multiply,
-
-		// formatting of PageInfoMode query string
-		"modeQueryString": modeQueryString,
-
-		// check whether to display third party section or not
-		"hasThirdParty": hasThirdParty,
-
-		// get the no. of columns to split the toc in search page
-		"tocColCount": tocColCount,
-	}
-	if p.URLForSrc != nil {
-		p.funcMap["srcLink"] = p.URLForSrc
-	}
-	if p.URLForSrcPos != nil {
-		p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
-	}
-	if p.URLForSrcQuery != nil {
-		p.funcMap["queryLink"] = p.URLForSrcQuery
-	}
-}
-
-func multiply(a, b int) int { return a * b }
-
-func filenameFunc(path string) string {
-	_, localname := pathpkg.Split(path)
-	return localname
-}
-
-func fileInfoNameFunc(fi os.FileInfo) string {
-	name := fi.Name()
-	if fi.IsDir() {
-		name += "/"
-	}
-	return name
-}
-
-func fileInfoTimeFunc(fi os.FileInfo) string {
-	if t := fi.ModTime(); t.Unix() != 0 {
-		return t.Local().String()
-	}
-	return "" // don't return epoch if time is obviously not set
-}
-
-// The strings in infoKinds must be properly html-escaped.
-var infoKinds = [nKinds]string{
-	PackageClause: "package&nbsp;clause",
-	ImportDecl:    "import&nbsp;decl",
-	ConstDecl:     "const&nbsp;decl",
-	TypeDecl:      "type&nbsp;decl",
-	VarDecl:       "var&nbsp;decl",
-	FuncDecl:      "func&nbsp;decl",
-	MethodDecl:    "method&nbsp;decl",
-	Use:           "use",
-}
-
-func infoKind_htmlFunc(info SpotInfo) string {
-	return infoKinds[info.Kind()] // infoKind entries are html-escaped
-}
-
-func (p *Presentation) infoLineFunc(info SpotInfo) int {
-	line := info.Lori()
-	if info.IsIndex() {
-		index, _ := p.Corpus.searchIndex.Get()
-		if index != nil {
-			line = index.(*Index).Snippet(line).Line
-		} else {
-			// no line information available because
-			// we don't have an index - this should
-			// never happen; be conservative and don't
-			// crash
-			line = 0
-		}
-	}
-	return line
-}
-
-func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
-	if info.IsIndex() {
-		index, _ := p.Corpus.searchIndex.Get()
-		// Snippet.Text was HTML-escaped when it was generated
-		return index.(*Index).Snippet(info.Lori()).Text
-	}
-	return `<span class="alert">no snippet text available</span>`
-}
-
-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
-	if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
-		LinkifyText(&buf2, buf1.Bytes(), n)
-		if st, name := isStructTypeDecl(n); st != nil {
-			addStructFieldIDAttributes(&buf2, name, st)
-		}
-	} else {
-		FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
-	}
-
-	return buf2.String()
-}
-
-// isStructTypeDecl checks whether n is a struct declaration.
-// It either returns a non-nil StructType and its name, or zero values.
-func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
-	gd, ok := n.(*ast.GenDecl)
-	if !ok || gd.Tok != token.TYPE {
-		return nil, ""
-	}
-	if gd.Lparen > 0 {
-		// Parenthesized type. Who does that, anyway?
-		// TODO: Reportedly gri does. Fix this to handle that too.
-		return nil, ""
-	}
-	if len(gd.Specs) != 1 {
-		return nil, ""
-	}
-	ts, ok := gd.Specs[0].(*ast.TypeSpec)
-	if !ok {
-		return nil, ""
-	}
-	st, ok = ts.Type.(*ast.StructType)
-	if !ok {
-		return nil, ""
-	}
-	return st, ts.Name.Name
-}
-
-// addStructFieldIDAttributes modifies the contents of buf such that
-// all struct fields of the named struct have <span id='name.Field'>
-// in them, so people can link to /#Struct.Field.
-func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
-	if st.Fields == nil {
-		return
-	}
-	// needsLink is a set of identifiers that still need to be
-	// linked, where value == key, to avoid an allocation in func
-	// linkedField.
-	needsLink := make(map[string]string)
-
-	for _, f := range st.Fields.List {
-		if len(f.Names) == 0 {
-			continue
-		}
-		fieldName := f.Names[0].Name
-		needsLink[fieldName] = fieldName
-	}
-	var newBuf bytes.Buffer
-	foreachLine(buf.Bytes(), func(line []byte) {
-		if fieldName := linkedField(line, needsLink); fieldName != "" {
-			fmt.Fprintf(&newBuf, `<span id="%s.%s"></span>`, name, fieldName)
-			delete(needsLink, fieldName)
-		}
-		newBuf.Write(line)
-	})
-	buf.Reset()
-	buf.Write(newBuf.Bytes())
-}
-
-// foreachLine calls fn for each line of in, where a line includes
-// the trailing "\n", except on the last line, if it doesn't exist.
-func foreachLine(in []byte, fn func(line []byte)) {
-	for len(in) > 0 {
-		nl := bytes.IndexByte(in, '\n')
-		if nl == -1 {
-			fn(in)
-			return
-		}
-		fn(in[:nl+1])
-		in = in[nl+1:]
-	}
-}
-
-// commentPrefix is the line prefix for comments after they've been HTMLified.
-var commentPrefix = []byte(`<span class="comment">// `)
-
-// linkedField determines whether the given line starts with an
-// identifer in the provided ids map (mapping from identifier to the
-// same identifier). The line can start with either an identifier or
-// an identifier in a comment. If one matches, it returns the
-// identifier that matched. Otherwise it returns the empty string.
-func linkedField(line []byte, ids map[string]string) string {
-	line = bytes.TrimSpace(line)
-
-	// For fields with a doc string of the
-	// conventional form, we put the new span into
-	// the comment instead of the field.
-	// The "conventional" form is a complete sentence
-	// per https://golang.org/s/style#comment-sentences like:
-	//
-	//    // Foo is an optional Fooer to foo the foos.
-	//    Foo Fooer
-	//
-	// In this case, we want the #StructName.Foo
-	// link to make the browser go to the comment
-	// line "Foo is an optional Fooer" instead of
-	// the "Foo Fooer" line, which could otherwise
-	// obscure the docs above the browser's "fold".
-	//
-	// TODO: do this better, so it works for all
-	// comments, including unconventional ones.
-	if bytes.HasPrefix(line, commentPrefix) {
-		line = line[len(commentPrefix):]
-	}
-	id := scanIdentifier(line)
-	if len(id) == 0 {
-		// No leading identifier. Avoid map lookup for
-		// somewhat common case.
-		return ""
-	}
-	return ids[string(id)]
-}
-
-// scanIdentifier scans a valid Go identifier off the front of v and
-// either returns a subslice of v if there's a valid identifier, or
-// returns a zero-length slice.
-func scanIdentifier(v []byte) []byte {
-	var n int // number of leading bytes of v belonging to an identifier
-	for {
-		r, width := utf8.DecodeRune(v[n:])
-		if !(isLetter(r) || n > 0 && isDigit(r)) {
-			break
-		}
-		n += width
-	}
-	return v[:n]
-}
-
-func isLetter(ch rune) bool {
-	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
-}
-
-func isDigit(ch rune) bool {
-	return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
-}
-
-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()
-}
-
-// punchCardWidth is the number of columns of fixed-width
-// characters to assume when wrapping text.  Very few people
-// use terminals or cards smaller than 80 characters, so 80 it is.
-// We do not try to sniff the environment or the tty to adapt to
-// the situation; instead, by using a constant we make sure that
-// godoc always produces the same output regardless of context,
-// a consistency that is lost otherwise.  For example, if we sniffed
-// the environment or tty, then http://golang.org/pkg/math/?m=text
-// would depend on the width of the terminal where godoc started,
-// which is clearly bogus.  More generally, the Unix tools that behave
-// differently when writing to a tty than when writing to a file have
-// a history of causing confusion (compare `ls` and `ls | cat`), and we
-// want to avoid that mistake here.
-const punchCardWidth = 80
-
-func containsOnlySpace(buf []byte) bool {
-	isNotSpace := func(r rune) bool { return !unicode.IsSpace(r) }
-	return bytes.IndexFunc(buf, isNotSpace) == -1
-}
-
-// 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
-	GoogleCN bool   // page is being served from golang.google.cn
-
-	Mode PageInfoMode // display metadata from query string
-
-	// package info
-	FSet       *token.FileSet         // nil if no package documentation
-	PDoc       *doc.Package           // nil if no package documentation
-	Examples   []*doc.Example         // nil if no example code
-	Notes      map[string][]*doc.Note // nil if no package Notes
-	PAst       map[string]*ast.File   // nil if no AST with package exports
-	IsMain     bool                   // true for package main
-	IsFiltered bool                   // true if results were filtered
-
-	// analysis info
-	TypeInfoIndex  map[string]int  // index of JSON datum for type T (if -analysis=type)
-	AnalysisData   htmltemplate.JS // array of TypeInfoJSON values
-	CallGraph      htmltemplate.JS // array of PCGNodeJSON values    (if -analysis=pointer)
-	CallGraphIndex map[string]int  // maps func name to index in CallGraph
-
-	// directory info
-	Dirs    *DirList  // nil if no directory information
-	DirTime time.Time // directory time stamp
-	DirFlat bool      // if set, show directory in a flat (non-indented) manner
-}
-
-func (info *PageInfo) IsEmpty() bool {
-	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
-}
-
-func pkgLinkFunc(path string) string {
-	// because of the irregular mapping under goroot
-	// we need to correct certain relative paths
-	path = strings.TrimPrefix(path, "/")
-	path = strings.TrimPrefix(path, "src/")
-	path = strings.TrimPrefix(path, "pkg/")
-	return "pkg/" + path
-}
-
-// srcToPkgLinkFunc builds an <a> tag linking to the package
-// documentation of relpath.
-func srcToPkgLinkFunc(relpath string) string {
-	relpath = pkgLinkFunc(relpath)
-	relpath = pathpkg.Dir(relpath)
-	if relpath == "pkg" {
-		return `<a href="/pkg">Index</a>`
-	}
-	return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
-}
-
-// srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
-// Each segment links to its corresponding src directories.
-func srcBreadcrumbFunc(relpath string) string {
-	segments := strings.Split(relpath, "/")
-	var buf bytes.Buffer
-	var selectedSegment string
-	var selectedIndex int
-
-	if strings.HasSuffix(relpath, "/") {
-		// relpath is a directory ending with a "/".
-		// Selected segment is the segment before the last slash.
-		selectedIndex = len(segments) - 2
-		selectedSegment = segments[selectedIndex] + "/"
-	} else {
-		selectedIndex = len(segments) - 1
-		selectedSegment = segments[selectedIndex]
-	}
-
-	for i := range segments[:selectedIndex] {
-		buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
-			strings.Join(segments[:i+1], "/"),
-			segments[i],
-		))
-	}
-
-	buf.WriteString(`<span class="text-muted">`)
-	buf.WriteString(selectedSegment)
-	buf.WriteString(`</span>`)
-	return buf.String()
-}
-
-func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
-	// n must be an ast.Node or a *doc.Note
-	return func(info *PageInfo, n interface{}) string {
-		var pos, end token.Pos
-
-		switch n := n.(type) {
-		case ast.Node:
-			pos = n.Pos()
-			end = n.End()
-		case *doc.Note:
-			pos = n.Pos
-			end = n.End
-		default:
-			panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
-		}
-
-		var relpath string
-		var line int
-		var low, high int // selection offset range
-
-		if pos.IsValid() {
-			p := info.FSet.Position(pos)
-			relpath = p.Filename
-			line = p.Line
-			low = p.Offset
-		}
-		if end.IsValid() {
-			high = info.FSet.Position(end).Offset
-		}
-
-		return srcPosLinkFunc(relpath, line, low, high)
-	}
-}
-
-func srcPosLinkFunc(s string, line, low, high int) string {
-	s = srcLinkFunc(s)
-	var buf bytes.Buffer
-	template.HTMLEscape(&buf, []byte(s))
-	// selection ranges are of form "s=low:high"
-	if low < high {
-		fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
-		// if we have a selection, position the page
-		// such that the selection is a bit below the top
-		line -= 10
-		if line < 1 {
-			line = 1
-		}
-	}
-	// line id's in html-printed source are of the
-	// form "L%d" where %d stands for the line number
-	if line > 0 {
-		fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
-	}
-	return buf.String()
-}
-
-func srcLinkFunc(s string) string {
-	s = pathpkg.Clean("/" + s)
-	if !strings.HasPrefix(s, "/src/") {
-		s = "/src" + s
-	}
-	return s
-}
-
-// queryLinkFunc returns a URL for a line in a source file with a highlighted
-// query term.
-// s is expected to be a path to a source file.
-// query is expected to be a string that has already been appropriately escaped
-// for use in a URL query.
-func queryLinkFunc(s, query string, line int) string {
-	url := pathpkg.Clean("/"+s) + "?h=" + query
-	if line > 0 {
-		url += "#L" + strconv.Itoa(line)
-	}
-	return url
-}
-
-func docLinkFunc(s string, ident string) string {
-	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
-}
-
-// implements_html returns the "> Implements" toggle for a package-level named type.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
-	if p.ImplementsHTML == nil {
-		return ""
-	}
-	index, ok := info.TypeInfoIndex[typeName]
-	if !ok {
-		return ""
-	}
-	var buf bytes.Buffer
-	err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
-	if err != nil {
-		log.Print(err)
-	}
-	return buf.String()
-}
-
-// methodset_html returns the "> Method set" toggle for a package-level named type.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
-	if p.MethodSetHTML == nil {
-		return ""
-	}
-	index, ok := info.TypeInfoIndex[typeName]
-	if !ok {
-		return ""
-	}
-	var buf bytes.Buffer
-	err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
-	if err != nil {
-		log.Print(err)
-	}
-	return buf.String()
-}
-
-// callgraph_html returns the "> Call graph" toggle for a package-level func.
-// Its contents are populated from JSON data by client-side JS at load time.
-func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
-	if p.CallGraphHTML == nil {
-		return ""
-	}
-	if recv != "" {
-		// Format must match (*ssa.Function).RelString().
-		name = fmt.Sprintf("(%s).%s", recv, name)
-	}
-	index, ok := info.CallGraphIndex[name]
-	if !ok {
-		return ""
-	}
-	var buf bytes.Buffer
-	err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
-	if err != nil {
-		log.Print(err)
-	}
-	return buf.String()
-}
-
-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(&tconv{p: p, output: out}, fset, x)
-	if err != nil {
-		log.Print(err)
-	}
-
-	// Add comments to struct fields saying which Go version introducd 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])
-}
diff --git a/cmd/golangorg/godoc/godoc17_test.go b/cmd/golangorg/godoc/godoc17_test.go
deleted file mode 100644
index d153991..0000000
--- a/cmd/golangorg/godoc/godoc17_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2017 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.
-
-// +build go1.7
-
-package godoc
-
-import (
-	"bytes"
-	"fmt"
-	"testing"
-)
-
-// Verify that scanIdentifier isn't quadratic.
-// This doesn't actually measure and fail on its own, but it was previously
-// very obvious when running by hand.
-//
-// TODO: if there's a reliable and non-flaky way to test this, do so.
-// Maybe count user CPU time instead of wall time? But that's not easy
-// to do portably in Go.
-func TestStructField(t *testing.T) {
-	for _, n := range []int{10, 100, 1000, 10000} {
-		n := n
-		t.Run(fmt.Sprint(n), func(t *testing.T) {
-			var buf bytes.Buffer
-			fmt.Fprintf(&buf, "package foo\n\ntype T struct {\n")
-			for i := 0; i < n; i++ {
-				fmt.Fprintf(&buf, "\t// Field%d is foo.\n\tField%d int\n\n", i, i)
-			}
-			fmt.Fprintf(&buf, "}\n")
-			linkifySource(t, buf.Bytes())
-		})
-	}
-}
diff --git a/cmd/golangorg/godoc/godoc_test.go b/cmd/golangorg/godoc/godoc_test.go
deleted file mode 100644
index 33dbe3f..0000000
--- a/cmd/golangorg/godoc/godoc_test.go
+++ /dev/null
@@ -1,370 +0,0 @@
-// 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.
-
-package godoc
-
-import (
-	"bytes"
-	"go/parser"
-	"go/token"
-	"strings"
-	"testing"
-)
-
-func TestPkgLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want string
-	}{
-		{"/src/fmt", "pkg/fmt"},
-		{"src/fmt", "pkg/fmt"},
-		{"/fmt", "pkg/fmt"},
-		{"fmt", "pkg/fmt"},
-	} {
-		if got := pkgLinkFunc(tc.path); got != tc.want {
-			t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}
-
-func TestSrcPosLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		src  string
-		line int
-		low  int
-		high int
-		want string
-	}{
-		{"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"},
-		{"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
-		{"/src/fmt/print.go", 2, 0, 0, "/src/fmt/print.go#L2"},
-		{"/src/fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
-		{"/src/fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
-		{"fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
-		{"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
-	} {
-		if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want {
-			t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want)
-		}
-	}
-}
-
-func TestSrcLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		src  string
-		want string
-	}{
-		{"/src/fmt/print.go", "/src/fmt/print.go"},
-		{"src/fmt/print.go", "/src/fmt/print.go"},
-		{"/fmt/print.go", "/src/fmt/print.go"},
-		{"fmt/print.go", "/src/fmt/print.go"},
-	} {
-		if got := srcLinkFunc(tc.src); got != tc.want {
-			t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want)
-		}
-	}
-}
-
-func TestQueryLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		src   string
-		query string
-		line  int
-		want  string
-	}{
-		{"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"},
-		{"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"},
-		{"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"},
-		{"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"},
-	} {
-		if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want {
-			t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want)
-		}
-	}
-}
-
-func TestDocLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		src   string
-		ident string
-		want  string
-	}{
-		{"fmt", "Sprintf", "/pkg/fmt/#Sprintf"},
-		{"fmt", "EOF", "/pkg/fmt/#EOF"},
-	} {
-		if got := docLinkFunc(tc.src, tc.ident); got != tc.want {
-			t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want)
-		}
-	}
-}
-
-func TestSanitizeFunc(t *testing.T) {
-	for _, tc := range []struct {
-		src  string
-		want string
-	}{
-		{},
-		{"foo", "foo"},
-		{"func   f()", "func f()"},
-		{"func f(a int,)", "func f(a int)"},
-		{"func f(a int,\n)", "func f(a int)"},
-		{"func f(\n\ta int,\n\tb int,\n\tc int,\n)", "func f(a int, b int, c int)"},
-		{"  (   a,   b,  c  )  ", "(a, b, c)"},
-		{"(  a,  b, c    int, foo   bar  ,  )", "(a, b, c int, foo bar)"},
-		{"{   a,   b}", "{a, b}"},
-		{"[   a,   b]", "[a, b]"},
-	} {
-		if got := sanitizeFunc(tc.src); got != tc.want {
-			t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want)
-		}
-	}
-}
-
-// Test that we add <span id="StructName.FieldName"> elements
-// to the HTML of struct fields.
-func TestStructFieldsIDAttributes(t *testing.T) {
-	got := linkifySource(t, []byte(`
-package foo
-
-type T struct {
-	NoDoc string
-
-	// Doc has a comment.
-	Doc string
-
-	// Opt, if non-nil, is an option.
-	Opt *int
-
-	// Опция - другое поле.
-	Опция bool
-}
-`))
-	want := `type T struct {
-<span id="T.NoDoc"></span>NoDoc <a href="/pkg/builtin/#string">string</a>
-
-<span id="T.Doc"></span><span class="comment">// Doc has a comment.</span>
-Doc <a href="/pkg/builtin/#string">string</a>
-
-<span id="T.Opt"></span><span class="comment">// Opt, if non-nil, is an option.</span>
-Opt *<a href="/pkg/builtin/#int">int</a>
-
-<span id="T.Опция"></span><span class="comment">// Опция - другое поле.</span>
-Опция <a href="/pkg/builtin/#bool">bool</a>
-}`
-	if got != want {
-		t.Errorf("got: %s\n\nwant: %s\n", got, want)
-	}
-}
-
-// Test that we add <span id="ConstName"> elements to the HTML
-// of definitions in const and var specs.
-func TestValueSpecIDAttributes(t *testing.T) {
-	got := linkifySource(t, []byte(`
-package foo
-
-const (
-	NoDoc string = "NoDoc"
-
-	// Doc has a comment
-	Doc = "Doc"
-
-	NoVal
-)`))
-	want := `const (
-<span id="NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a> = &#34;NoDoc&#34;
-
-<span class="comment">// Doc has a comment</span>
-<span id="Doc">Doc</span> = &#34;Doc&#34;
-
-<span id="NoVal">NoVal</span>
-)`
-	if got != want {
-		t.Errorf("got: %s\n\nwant: %s\n", got, want)
-	}
-}
-
-func TestCompositeLitLinkFields(t *testing.T) {
-	got := linkifySource(t, []byte(`
-package foo
-
-type T struct {
-	X int
-}
-
-var S T = T{X: 12}`))
-	want := `type T struct {
-<span id="T.X"></span>X <a href="/pkg/builtin/#int">int</a>
-}
-var <span id="S">S</span> <a href="#T">T</a> = <a href="#T">T</a>{<a href="#T.X">X</a>: 12}`
-	if got != want {
-		t.Errorf("got: %s\n\nwant: %s\n", got, want)
-	}
-}
-
-func TestFuncDeclNotLink(t *testing.T) {
-	// Function.
-	got := linkifySource(t, []byte(`
-package http
-
-func Get(url string) (resp *Response, err error)`))
-	want := `func Get(url <a href="/pkg/builtin/#string">string</a>) (resp *<a href="#Response">Response</a>, err <a href="/pkg/builtin/#error">error</a>)`
-	if got != want {
-		t.Errorf("got: %s\n\nwant: %s\n", got, want)
-	}
-
-	// Method.
-	got = linkifySource(t, []byte(`
-package http
-
-func (h Header) Get(key string) string`))
-	want = `func (h <a href="#Header">Header</a>) Get(key <a href="/pkg/builtin/#string">string</a>) <a href="/pkg/builtin/#string">string</a>`
-	if got != want {
-		t.Errorf("got: %s\n\nwant: %s\n", got, want)
-	}
-}
-
-func linkifySource(t *testing.T, src []byte) string {
-	p := &Presentation{
-		DeclLinks: true,
-	}
-	fset := token.NewFileSet()
-	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
-	if err != nil {
-		t.Fatal(err)
-	}
-	var buf bytes.Buffer
-	pi := &PageInfo{
-		FSet: fset,
-	}
-	sep := ""
-	for _, decl := range af.Decls {
-		buf.WriteString(sep)
-		sep = "\n"
-		buf.WriteString(p.node_htmlFunc(pi, decl, true))
-	}
-	return buf.String()
-}
-
-func TestScanIdentifier(t *testing.T) {
-	tests := []struct {
-		in, want string
-	}{
-		{"foo bar", "foo"},
-		{"foo/bar", "foo"},
-		{" foo", ""},
-		{"фоо", "фоо"},
-		{"f123", "f123"},
-		{"123f", ""},
-	}
-	for _, tt := range tests {
-		got := scanIdentifier([]byte(tt.in))
-		if string(got) != tt.want {
-			t.Errorf("scanIdentifier(%q) = %q; want %q", tt.in, got, tt.want)
-		}
-	}
-}
-
-func TestReplaceLeadingIndentation(t *testing.T) {
-	oldIndent := strings.Repeat(" ", 2)
-	newIndent := strings.Repeat(" ", 4)
-	tests := []struct {
-		src, want string
-	}{
-		{"  foo\n    bar\n  baz", "    foo\n      bar\n    baz"},
-		{"  '`'\n  '`'\n", "    '`'\n    '`'\n"},
-		{"  '\\''\n  '`'\n", "    '\\''\n    '`'\n"},
-		{"  \"`\"\n  \"`\"\n", "    \"`\"\n    \"`\"\n"},
-		{"  `foo\n  bar`", "    `foo\n      bar`"},
-		{"  `foo\\`\n  bar", "    `foo\\`\n    bar"},
-		{"  '\\`'`foo\n  bar", "    '\\`'`foo\n      bar"},
-		{
-			"  if true {\n    foo := `One\n    \tTwo\nThree`\n  }\n",
-			"    if true {\n      foo := `One\n        \tTwo\n    Three`\n    }\n",
-		},
-	}
-	for _, tc := range tests {
-		if got := replaceLeadingIndentation(tc.src, oldIndent, newIndent); got != tc.want {
-			t.Errorf("replaceLeadingIndentation:\n%v\n---\nhave:\n%v\n---\nwant:\n%v\n",
-				tc.src, got, tc.want)
-		}
-	}
-}
-
-func TestSrcBreadcrumbFunc(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want string
-	}{
-		{"src/", `<span class="text-muted">src/</span>`},
-		{"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`},
-		{"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`},
-	} {
-		if got := srcBreadcrumbFunc(tc.path); got != tc.want {
-			t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}
-
-func TestSrcToPkgLinkFunc(t *testing.T) {
-	for _, tc := range []struct {
-		path string
-		want string
-	}{
-		{"src/", `<a href="/pkg">Index</a>`},
-		{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
-		{"pkg/", `<a href="/pkg">Index</a>`},
-		{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
-	} {
-		if got := srcToPkgLinkFunc(tc.path); got != tc.want {
-			t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
-		}
-	}
-}
-
-func TestFilterOutBuildAnnotations(t *testing.T) {
-	// TODO: simplify this by using a multiline string once we stop
-	// using go vet from 1.10 on the build dashboard.
-	// https://golang.org/issue/26627
-	src := []byte("// +build !foo\n" +
-		"// +build !anothertag\n" +
-		"\n" +
-		"// non-tag comment\n" +
-		"\n" +
-		"package foo\n" +
-		"\n" +
-		"func bar() int {\n" +
-		"	return 42\n" +
-		"}\n")
-
-	fset := token.NewFileSet()
-	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	var found bool
-	for _, cg := range af.Comments {
-		if strings.HasPrefix(cg.Text(), "+build ") {
-			found = true
-			break
-		}
-	}
-	if !found {
-		t.Errorf("TestFilterOutBuildAnnotations is broken: missing build tag in test input")
-	}
-
-	found = false
-	for _, cg := range filterOutBuildAnnotations(af.Comments) {
-		if strings.HasPrefix(cg.Text(), "+build ") {
-			t.Errorf("filterOutBuildAnnotations failed to filter build tag")
-		}
-
-		if strings.Contains(cg.Text(), "non-tag comment") {
-			found = true
-		}
-	}
-	if !found {
-		t.Errorf("filterOutBuildAnnotations should not remove non-build tag comment")
-	}
-}
diff --git a/cmd/golangorg/godoc/index.go b/cmd/golangorg/godoc/index.go
deleted file mode 100644
index ee54447..0000000
--- a/cmd/golangorg/godoc/index.go
+++ /dev/null
@@ -1,1580 +0,0 @@
-// Copyright 2009 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 contains the infrastructure to create an
-// identifier and full-text index for a set of Go files.
-//
-// Algorithm for identifier index:
-// - traverse all .go files of the file tree specified by root
-// - for each identifier (word) encountered, collect all occurrences (spots)
-//   into a list; this produces a list of spots for each word
-// - reduce the lists: from a list of spots to a list of FileRuns,
-//   and from a list of FileRuns into a list of PakRuns
-// - make a HitList from the PakRuns
-//
-// Details:
-// - keep two lists per word: one containing package-level declarations
-//   that have snippets, and one containing all other spots
-// - keep the snippets in a separate table indexed by snippet index
-//   and store the snippet index in place of the line number in a SpotInfo
-//   (the line number for spots with snippets is stored in the snippet)
-// - at the end, create lists of alternative spellings for a given
-//   word
-//
-// Algorithm for full text index:
-// - concatenate all source code in a byte buffer (in memory)
-// - add the files to a file set in lockstep as they are added to the byte
-//   buffer such that a byte buffer offset corresponds to the Pos value for
-//   that file location
-// - create a suffix array from the concatenated sources
-//
-// String lookup in full text index:
-// - use the suffix array to lookup a string's offsets - the offsets
-//   correspond to the Pos values relative to the file set
-// - translate the Pos values back into file and line information and
-//   sort the result
-
-package godoc
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/gob"
-	"errors"
-	"fmt"
-	"go/ast"
-	"go/doc"
-	"go/parser"
-	"go/token"
-	"index/suffixarray"
-	"io"
-	"log"
-	"os"
-	pathpkg "path"
-	"path/filepath"
-	"regexp"
-	"runtime"
-	"sort"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
-	"unicode"
-
-	"golang.org/x/website/cmd/golangorg/godoc/util"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// ----------------------------------------------------------------------------
-// InterfaceSlice is a helper type for sorting interface
-// slices according to some slice-specific sort criteria.
-
-type comparer func(x, y interface{}) bool
-
-type interfaceSlice struct {
-	slice []interface{}
-	less  comparer
-}
-
-// ----------------------------------------------------------------------------
-// RunList
-
-// A RunList is a list of entries that can be sorted according to some
-// criteria. A RunList may be compressed by grouping "runs" of entries
-// which are equal (according to the sort criteria) into a new RunList of
-// runs. For instance, a RunList containing pairs (x, y) may be compressed
-// into a RunList containing pair runs (x, {y}) where each run consists of
-// a list of y's with the same x.
-type RunList []interface{}
-
-func (h RunList) sort(less comparer) {
-	sort.Sort(&interfaceSlice{h, less})
-}
-
-func (p *interfaceSlice) Len() int           { return len(p.slice) }
-func (p *interfaceSlice) Less(i, j int) bool { return p.less(p.slice[i], p.slice[j]) }
-func (p *interfaceSlice) Swap(i, j int)      { p.slice[i], p.slice[j] = p.slice[j], p.slice[i] }
-
-// Compress entries which are the same according to a sort criteria
-// (specified by less) into "runs".
-func (h RunList) reduce(less comparer, newRun func(h RunList) interface{}) RunList {
-	if len(h) == 0 {
-		return nil
-	}
-	// len(h) > 0
-
-	// create runs of entries with equal values
-	h.sort(less)
-
-	// for each run, make a new run object and collect them in a new RunList
-	var hh RunList
-	i, x := 0, h[0]
-	for j, y := range h {
-		if less(x, y) {
-			hh = append(hh, newRun(h[i:j]))
-			i, x = j, h[j] // start a new run
-		}
-	}
-	// add final run, if any
-	if i < len(h) {
-		hh = append(hh, newRun(h[i:]))
-	}
-
-	return hh
-}
-
-// ----------------------------------------------------------------------------
-// KindRun
-
-// Debugging support. Disable to see multiple entries per line.
-const removeDuplicates = true
-
-// A KindRun is a run of SpotInfos of the same kind in a given file.
-// The kind (3 bits) is stored in each SpotInfo element; to find the
-// kind of a KindRun, look at any of its elements.
-type KindRun []SpotInfo
-
-// KindRuns are sorted by line number or index. Since the isIndex bit
-// is always the same for all infos in one list we can compare lori's.
-func (k KindRun) Len() int           { return len(k) }
-func (k KindRun) Less(i, j int) bool { return k[i].Lori() < k[j].Lori() }
-func (k KindRun) Swap(i, j int)      { k[i], k[j] = k[j], k[i] }
-
-// FileRun contents are sorted by Kind for the reduction into KindRuns.
-func lessKind(x, y interface{}) bool { return x.(SpotInfo).Kind() < y.(SpotInfo).Kind() }
-
-// newKindRun allocates a new KindRun from the SpotInfo run h.
-func newKindRun(h RunList) interface{} {
-	run := make(KindRun, len(h))
-	for i, x := range h {
-		run[i] = x.(SpotInfo)
-	}
-
-	// Spots were sorted by file and kind to create this run.
-	// Within this run, sort them by line number or index.
-	sort.Sort(run)
-
-	if removeDuplicates {
-		// Since both the lori and kind field must be
-		// same for duplicates, and since the isIndex
-		// bit is always the same for all infos in one
-		// list we can simply compare the entire info.
-		k := 0
-		prev := SpotInfo(1<<32 - 1) // an unlikely value
-		for _, x := range run {
-			if x != prev {
-				run[k] = x
-				k++
-				prev = x
-			}
-		}
-		run = run[0:k]
-	}
-
-	return run
-}
-
-// ----------------------------------------------------------------------------
-// FileRun
-
-// A Pak describes a Go package.
-type Pak struct {
-	Path string // path of directory containing the package
-	Name string // package name as declared by package clause
-}
-
-// Paks are sorted by name (primary key) and by import path (secondary key).
-func (p *Pak) less(q *Pak) bool {
-	return p.Name < q.Name || p.Name == q.Name && p.Path < q.Path
-}
-
-// A File describes a Go file.
-type File struct {
-	Name string // directory-local file name
-	Pak  *Pak   // the package to which the file belongs
-}
-
-// Path returns the file path of f.
-func (f *File) Path() string {
-	return pathpkg.Join(f.Pak.Path, f.Name)
-}
-
-// A Spot describes a single occurrence of a word.
-type Spot struct {
-	File *File
-	Info SpotInfo
-}
-
-// A FileRun is a list of KindRuns belonging to the same file.
-type FileRun struct {
-	File   *File
-	Groups []KindRun
-}
-
-// Spots are sorted by file path for the reduction into FileRuns.
-func lessSpot(x, y interface{}) bool {
-	fx := x.(Spot).File
-	fy := y.(Spot).File
-	// same as "return fx.Path() < fy.Path()" but w/o computing the file path first
-	px := fx.Pak.Path
-	py := fy.Pak.Path
-	return px < py || px == py && fx.Name < fy.Name
-}
-
-// newFileRun allocates a new FileRun from the Spot run h.
-func newFileRun(h RunList) interface{} {
-	file := h[0].(Spot).File
-
-	// reduce the list of Spots into a list of KindRuns
-	h1 := make(RunList, len(h))
-	for i, x := range h {
-		h1[i] = x.(Spot).Info
-	}
-	h2 := h1.reduce(lessKind, newKindRun)
-
-	// create the FileRun
-	groups := make([]KindRun, len(h2))
-	for i, x := range h2 {
-		groups[i] = x.(KindRun)
-	}
-	return &FileRun{file, groups}
-}
-
-// ----------------------------------------------------------------------------
-// PakRun
-
-// A PakRun describes a run of *FileRuns of a package.
-type PakRun struct {
-	Pak   *Pak
-	Files []*FileRun
-}
-
-// Sorting support for files within a PakRun.
-func (p *PakRun) Len() int           { return len(p.Files) }
-func (p *PakRun) Less(i, j int) bool { return p.Files[i].File.Name < p.Files[j].File.Name }
-func (p *PakRun) Swap(i, j int)      { p.Files[i], p.Files[j] = p.Files[j], p.Files[i] }
-
-// FileRuns are sorted by package for the reduction into PakRuns.
-func lessFileRun(x, y interface{}) bool {
-	return x.(*FileRun).File.Pak.less(y.(*FileRun).File.Pak)
-}
-
-// newPakRun allocates a new PakRun from the *FileRun run h.
-func newPakRun(h RunList) interface{} {
-	pak := h[0].(*FileRun).File.Pak
-	files := make([]*FileRun, len(h))
-	for i, x := range h {
-		files[i] = x.(*FileRun)
-	}
-	run := &PakRun{pak, files}
-	sort.Sort(run) // files were sorted by package; sort them by file now
-	return run
-}
-
-// ----------------------------------------------------------------------------
-// HitList
-
-// A HitList describes a list of PakRuns.
-type HitList []*PakRun
-
-// PakRuns are sorted by package.
-func lessPakRun(x, y interface{}) bool { return x.(*PakRun).Pak.less(y.(*PakRun).Pak) }
-
-func reduce(h0 RunList) HitList {
-	// reduce a list of Spots into a list of FileRuns
-	h1 := h0.reduce(lessSpot, newFileRun)
-	// reduce a list of FileRuns into a list of PakRuns
-	h2 := h1.reduce(lessFileRun, newPakRun)
-	// sort the list of PakRuns by package
-	h2.sort(lessPakRun)
-	// create a HitList
-	h := make(HitList, len(h2))
-	for i, p := range h2 {
-		h[i] = p.(*PakRun)
-	}
-	return h
-}
-
-// filter returns a new HitList created by filtering
-// all PakRuns from h that have a matching pakname.
-func (h HitList) filter(pakname string) HitList {
-	var hh HitList
-	for _, p := range h {
-		if p.Pak.Name == pakname {
-			hh = append(hh, p)
-		}
-	}
-	return hh
-}
-
-// ----------------------------------------------------------------------------
-// AltWords
-
-type wordPair struct {
-	canon string // canonical word spelling (all lowercase)
-	alt   string // alternative spelling
-}
-
-// An AltWords describes a list of alternative spellings for a
-// canonical (all lowercase) spelling of a word.
-type AltWords struct {
-	Canon string   // canonical word spelling (all lowercase)
-	Alts  []string // alternative spelling for the same word
-}
-
-// wordPairs are sorted by their canonical spelling.
-func lessWordPair(x, y interface{}) bool { return x.(*wordPair).canon < y.(*wordPair).canon }
-
-// newAltWords allocates a new AltWords from the *wordPair run h.
-func newAltWords(h RunList) interface{} {
-	canon := h[0].(*wordPair).canon
-	alts := make([]string, len(h))
-	for i, x := range h {
-		alts[i] = x.(*wordPair).alt
-	}
-	return &AltWords{canon, alts}
-}
-
-func (a *AltWords) filter(s string) *AltWords {
-	var alts []string
-	for _, w := range a.Alts {
-		if w != s {
-			alts = append(alts, w)
-		}
-	}
-	if len(alts) > 0 {
-		return &AltWords{a.Canon, alts}
-	}
-	return nil
-}
-
-// Ident stores information about external identifiers in order to create
-// links to package documentation.
-type Ident struct {
-	Path    string // e.g. "net/http"
-	Package string // e.g. "http"
-	Name    string // e.g. "NewRequest"
-	Doc     string // e.g. "NewRequest returns a new Request..."
-}
-
-// byImportCount sorts the given slice of Idents by the import
-// counts of the packages to which they belong.
-type byImportCount struct {
-	Idents      []Ident
-	ImportCount map[string]int
-}
-
-func (ic byImportCount) Len() int {
-	return len(ic.Idents)
-}
-
-func (ic byImportCount) Less(i, j int) bool {
-	ri := ic.ImportCount[ic.Idents[i].Path]
-	rj := ic.ImportCount[ic.Idents[j].Path]
-	if ri == rj {
-		return ic.Idents[i].Path < ic.Idents[j].Path
-	}
-	return ri > rj
-}
-
-func (ic byImportCount) Swap(i, j int) {
-	ic.Idents[i], ic.Idents[j] = ic.Idents[j], ic.Idents[i]
-}
-
-func (ic byImportCount) String() string {
-	buf := bytes.NewBuffer([]byte("["))
-	for _, v := range ic.Idents {
-		buf.WriteString(fmt.Sprintf("\n\t%s, %s (%d)", v.Path, v.Name, ic.ImportCount[v.Path]))
-	}
-	buf.WriteString("\n]")
-	return buf.String()
-}
-
-// filter creates a new Ident list where the results match the given
-// package name.
-func (ic byImportCount) filter(pakname string) []Ident {
-	if ic.Idents == nil {
-		return nil
-	}
-	var res []Ident
-	for _, i := range ic.Idents {
-		if i.Package == pakname {
-			res = append(res, i)
-		}
-	}
-	return res
-}
-
-// top returns the top n identifiers.
-func (ic byImportCount) top(n int) []Ident {
-	if len(ic.Idents) > n {
-		return ic.Idents[:n]
-	}
-	return ic.Idents
-}
-
-// ----------------------------------------------------------------------------
-// Indexer
-
-type IndexResult struct {
-	Decls  RunList // package-level declarations (with snippets)
-	Others RunList // all other occurrences
-}
-
-// Statistics provides statistics information for an index.
-type Statistics struct {
-	Bytes int // total size of indexed source files
-	Files int // number of indexed source files
-	Lines int // number of lines (all files)
-	Words int // number of different identifiers
-	Spots int // number of identifier occurrences
-}
-
-// An Indexer maintains the data structures and provides the machinery
-// for indexing .go files under a file tree. It implements the path.Visitor
-// interface for walking file trees, and the ast.Visitor interface for
-// walking Go ASTs.
-type Indexer struct {
-	c          *Corpus
-	fset       *token.FileSet // file set for all indexed files
-	fsOpenGate chan bool      // send pre fs.Open; receive on close
-
-	mu            sync.Mutex              // guards all the following
-	sources       bytes.Buffer            // concatenated sources
-	strings       map[string]string       // interned string
-	packages      map[Pak]*Pak            // interned *Paks
-	words         map[string]*IndexResult // RunLists of Spots
-	snippets      []*Snippet              // indices are stored in SpotInfos
-	current       *token.File             // last file added to file set
-	file          *File                   // AST for current file
-	decl          ast.Decl                // AST for current decl
-	stats         Statistics
-	throttle      *util.Throttle
-	importCount   map[string]int                 // package path ("net/http") => count
-	packagePath   map[string]map[string]bool     // "template" => "text/template" => true
-	exports       map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
-	curPkgExports map[string]SpotKind
-	idents        map[SpotKind]map[string][]Ident // kind => name => list of Idents
-}
-
-func (x *Indexer) intern(s string) string {
-	if s, ok := x.strings[s]; ok {
-		return s
-	}
-	x.strings[s] = s
-	return s
-}
-
-func (x *Indexer) lookupPackage(path, name string) *Pak {
-	// In the source directory tree, more than one package may
-	// live in the same directory. For the packages map, construct
-	// a key that includes both the directory path and the package
-	// name.
-	key := Pak{Path: x.intern(path), Name: x.intern(name)}
-	pak := x.packages[key]
-	if pak == nil {
-		pak = &key
-		x.packages[key] = pak
-	}
-	return pak
-}
-
-func (x *Indexer) addSnippet(s *Snippet) int {
-	index := len(x.snippets)
-	x.snippets = append(x.snippets, s)
-	return index
-}
-
-func (x *Indexer) visitIdent(kind SpotKind, id *ast.Ident) {
-	if id == nil {
-		return
-	}
-	name := x.intern(id.Name)
-
-	switch kind {
-	case TypeDecl, FuncDecl, ConstDecl, VarDecl:
-		x.curPkgExports[name] = kind
-	}
-
-	lists, found := x.words[name]
-	if !found {
-		lists = new(IndexResult)
-		x.words[name] = lists
-	}
-
-	if kind == Use || x.decl == nil {
-		if x.c.IndexGoCode {
-			// not a declaration or no snippet required
-			info := makeSpotInfo(kind, x.current.Line(id.Pos()), false)
-			lists.Others = append(lists.Others, Spot{x.file, info})
-		}
-	} else {
-		// a declaration with snippet
-		index := x.addSnippet(NewSnippet(x.fset, x.decl, id))
-		info := makeSpotInfo(kind, index, true)
-		lists.Decls = append(lists.Decls, Spot{x.file, info})
-	}
-
-	x.stats.Spots++
-}
-
-func (x *Indexer) visitFieldList(kind SpotKind, flist *ast.FieldList) {
-	for _, f := range flist.List {
-		x.decl = nil // no snippets for fields
-		for _, name := range f.Names {
-			x.visitIdent(kind, name)
-		}
-		ast.Walk(x, f.Type)
-		// ignore tag - not indexed at the moment
-	}
-}
-
-func (x *Indexer) visitSpec(kind SpotKind, spec ast.Spec) {
-	switch n := spec.(type) {
-	case *ast.ImportSpec:
-		x.visitIdent(ImportDecl, n.Name)
-		if n.Path != nil {
-			if imp, err := strconv.Unquote(n.Path.Value); err == nil {
-				x.importCount[x.intern(imp)]++
-			}
-		}
-
-	case *ast.ValueSpec:
-		for _, n := range n.Names {
-			x.visitIdent(kind, n)
-		}
-		ast.Walk(x, n.Type)
-		for _, v := range n.Values {
-			ast.Walk(x, v)
-		}
-
-	case *ast.TypeSpec:
-		x.visitIdent(TypeDecl, n.Name)
-		ast.Walk(x, n.Type)
-	}
-}
-
-func (x *Indexer) visitGenDecl(decl *ast.GenDecl) {
-	kind := VarDecl
-	if decl.Tok == token.CONST {
-		kind = ConstDecl
-	}
-	x.decl = decl
-	for _, s := range decl.Specs {
-		x.visitSpec(kind, s)
-	}
-}
-
-func (x *Indexer) Visit(node ast.Node) ast.Visitor {
-	switch n := node.(type) {
-	case nil:
-		// nothing to do
-
-	case *ast.Ident:
-		x.visitIdent(Use, n)
-
-	case *ast.FieldList:
-		x.visitFieldList(VarDecl, n)
-
-	case *ast.InterfaceType:
-		x.visitFieldList(MethodDecl, n.Methods)
-
-	case *ast.DeclStmt:
-		// local declarations should only be *ast.GenDecls;
-		// ignore incorrect ASTs
-		if decl, ok := n.Decl.(*ast.GenDecl); ok {
-			x.decl = nil // no snippets for local declarations
-			x.visitGenDecl(decl)
-		}
-
-	case *ast.GenDecl:
-		x.decl = n
-		x.visitGenDecl(n)
-
-	case *ast.FuncDecl:
-		kind := FuncDecl
-		if n.Recv != nil {
-			kind = MethodDecl
-			ast.Walk(x, n.Recv)
-		}
-		x.decl = n
-		x.visitIdent(kind, n.Name)
-		ast.Walk(x, n.Type)
-		if n.Body != nil {
-			ast.Walk(x, n.Body)
-		}
-
-	case *ast.File:
-		x.decl = nil
-		x.visitIdent(PackageClause, n.Name)
-		for _, d := range n.Decls {
-			ast.Walk(x, d)
-		}
-
-	default:
-		return x
-	}
-
-	return nil
-}
-
-// addFile adds a file to the index if possible and returns the file set file
-// and the file's AST if it was successfully parsed as a Go file. If addFile
-// failed (that is, if the file was not added), it returns file == nil.
-func (x *Indexer) addFile(f vfs.ReadSeekCloser, filename string, goFile bool) (file *token.File, ast *ast.File) {
-	defer f.Close()
-
-	// The file set's base offset and x.sources size must be in lock-step;
-	// this permits the direct mapping of suffix array lookup results to
-	// to corresponding Pos values.
-	//
-	// When a file is added to the file set, its offset base increases by
-	// the size of the file + 1; and the initial base offset is 1. Add an
-	// extra byte to the sources here.
-	x.sources.WriteByte(0)
-
-	// If the sources length doesn't match the file set base at this point
-	// the file set implementation changed or we have another error.
-	base := x.fset.Base()
-	if x.sources.Len() != base {
-		panic("internal error: file base incorrect")
-	}
-
-	// append file contents (src) to x.sources
-	if _, err := x.sources.ReadFrom(f); err == nil {
-		src := x.sources.Bytes()[base:]
-
-		if goFile {
-			// parse the file and in the process add it to the file set
-			if ast, err = parser.ParseFile(x.fset, filename, src, parser.ParseComments); err == nil {
-				file = x.fset.File(ast.Pos()) // ast.Pos() is inside the file
-				return
-			}
-			// file has parse errors, and the AST may be incorrect -
-			// set lines information explicitly and index as ordinary
-			// text file (cannot fall through to the text case below
-			// because the file has already been added to the file set
-			// by the parser)
-			file = x.fset.File(token.Pos(base)) // token.Pos(base) is inside the file
-			file.SetLinesForContent(src)
-			ast = nil
-			return
-		}
-
-		if util.IsText(src) {
-			// only add the file to the file set (for the full text index)
-			file = x.fset.AddFile(filename, x.fset.Base(), len(src))
-			file.SetLinesForContent(src)
-			return
-		}
-	}
-
-	// discard possibly added data
-	x.sources.Truncate(base - 1) // -1 to remove added byte 0 since no file was added
-	return
-}
-
-// Design note: Using an explicit white list of permitted files for indexing
-// makes sure that the important files are included and massively reduces the
-// number of files to index. The advantage over a blacklist is that unexpected
-// (non-blacklisted) files won't suddenly explode the index.
-
-// Files are whitelisted if they have a file name or extension
-// present as key in whitelisted.
-var whitelisted = map[string]bool{
-	".bash":        true,
-	".c":           true,
-	".cc":          true,
-	".cpp":         true,
-	".cxx":         true,
-	".css":         true,
-	".go":          true,
-	".goc":         true,
-	".h":           true,
-	".hh":          true,
-	".hpp":         true,
-	".hxx":         true,
-	".html":        true,
-	".js":          true,
-	".out":         true,
-	".py":          true,
-	".s":           true,
-	".sh":          true,
-	".txt":         true,
-	".xml":         true,
-	"AUTHORS":      true,
-	"CONTRIBUTORS": true,
-	"LICENSE":      true,
-	"Makefile":     true,
-	"PATENTS":      true,
-	"README":       true,
-}
-
-// isWhitelisted returns true if a file is on the list
-// of "permitted" files for indexing. The filename must
-// be the directory-local name of the file.
-func isWhitelisted(filename string) bool {
-	key := pathpkg.Ext(filename)
-	if key == "" {
-		// file has no extension - use entire filename
-		key = filename
-	}
-	return whitelisted[key]
-}
-
-func (x *Indexer) indexDocs(dirname string, filename string, astFile *ast.File) {
-	pkgName := x.intern(astFile.Name.Name)
-	if pkgName == "main" {
-		return
-	}
-	pkgPath := x.intern(strings.TrimPrefix(strings.TrimPrefix(dirname, "/src/"), "pkg/"))
-	astPkg := ast.Package{
-		Name: pkgName,
-		Files: map[string]*ast.File{
-			filename: astFile,
-		},
-	}
-	var m doc.Mode
-	docPkg := doc.New(&astPkg, dirname, m)
-	addIdent := func(sk SpotKind, name string, docstr string) {
-		if x.idents[sk] == nil {
-			x.idents[sk] = make(map[string][]Ident)
-		}
-		name = x.intern(name)
-		x.idents[sk][name] = append(x.idents[sk][name], Ident{
-			Path:    pkgPath,
-			Package: pkgName,
-			Name:    name,
-			Doc:     doc.Synopsis(docstr),
-		})
-	}
-
-	if x.idents[PackageClause] == nil {
-		x.idents[PackageClause] = make(map[string][]Ident)
-	}
-	// List of words under which the package identifier will be stored.
-	// This includes the package name and the components of the directory
-	// in which it resides.
-	words := strings.Split(pathpkg.Dir(pkgPath), "/")
-	if words[0] == "." {
-		words = []string{}
-	}
-	name := x.intern(docPkg.Name)
-	synopsis := doc.Synopsis(docPkg.Doc)
-	words = append(words, name)
-	pkgIdent := Ident{
-		Path:    pkgPath,
-		Package: pkgName,
-		Name:    name,
-		Doc:     synopsis,
-	}
-	for _, word := range words {
-		word = x.intern(word)
-		found := false
-		pkgs := x.idents[PackageClause][word]
-		for i, p := range pkgs {
-			if p.Path == pkgPath {
-				if docPkg.Doc != "" {
-					p.Doc = synopsis
-					pkgs[i] = p
-				}
-				found = true
-				break
-			}
-		}
-		if !found {
-			x.idents[PackageClause][word] = append(x.idents[PackageClause][word], pkgIdent)
-		}
-	}
-
-	for _, c := range docPkg.Consts {
-		for _, name := range c.Names {
-			addIdent(ConstDecl, name, c.Doc)
-		}
-	}
-	for _, t := range docPkg.Types {
-		addIdent(TypeDecl, t.Name, t.Doc)
-		for _, c := range t.Consts {
-			for _, name := range c.Names {
-				addIdent(ConstDecl, name, c.Doc)
-			}
-		}
-		for _, v := range t.Vars {
-			for _, name := range v.Names {
-				addIdent(VarDecl, name, v.Doc)
-			}
-		}
-		for _, f := range t.Funcs {
-			addIdent(FuncDecl, f.Name, f.Doc)
-		}
-		for _, f := range t.Methods {
-			addIdent(MethodDecl, f.Name, f.Doc)
-			// Change the name of methods to be "<typename>.<methodname>".
-			// They will still be indexed as <methodname>.
-			idents := x.idents[MethodDecl][f.Name]
-			idents[len(idents)-1].Name = x.intern(t.Name + "." + f.Name)
-		}
-	}
-	for _, v := range docPkg.Vars {
-		for _, name := range v.Names {
-			addIdent(VarDecl, name, v.Doc)
-		}
-	}
-	for _, f := range docPkg.Funcs {
-		addIdent(FuncDecl, f.Name, f.Doc)
-	}
-}
-
-func (x *Indexer) indexGoFile(dirname string, filename string, file *token.File, astFile *ast.File) {
-	pkgName := astFile.Name.Name
-
-	if x.c.IndexGoCode {
-		x.current = file
-		pak := x.lookupPackage(dirname, pkgName)
-		x.file = &File{filename, pak}
-		ast.Walk(x, astFile)
-	}
-
-	if x.c.IndexDocs {
-		// Test files are already filtered out in visitFile if IndexGoCode and
-		// IndexFullText are false.  Otherwise, check here.
-		isTestFile := (x.c.IndexGoCode || x.c.IndexFullText) &&
-			(strings.HasSuffix(filename, "_test.go") || strings.HasPrefix(dirname, "/test/"))
-		if !isTestFile {
-			x.indexDocs(dirname, filename, astFile)
-		}
-	}
-
-	ppKey := x.intern(pkgName)
-	if _, ok := x.packagePath[ppKey]; !ok {
-		x.packagePath[ppKey] = make(map[string]bool)
-	}
-	pkgPath := x.intern(strings.TrimPrefix(strings.TrimPrefix(dirname, "/src/"), "pkg/"))
-	x.packagePath[ppKey][pkgPath] = true
-
-	// Merge in exported symbols found walking this file into
-	// the map for that package.
-	if len(x.curPkgExports) > 0 {
-		dest, ok := x.exports[pkgPath]
-		if !ok {
-			dest = make(map[string]SpotKind)
-			x.exports[pkgPath] = dest
-		}
-		for k, v := range x.curPkgExports {
-			dest[k] = v
-		}
-	}
-}
-
-func (x *Indexer) visitFile(dirname string, fi os.FileInfo) {
-	if fi.IsDir() || !x.c.IndexEnabled {
-		return
-	}
-
-	filename := pathpkg.Join(dirname, fi.Name())
-	goFile := isGoFile(fi)
-
-	switch {
-	case x.c.IndexFullText:
-		if !isWhitelisted(fi.Name()) {
-			return
-		}
-	case x.c.IndexGoCode:
-		if !goFile {
-			return
-		}
-	case x.c.IndexDocs:
-		if !goFile ||
-			strings.HasSuffix(fi.Name(), "_test.go") ||
-			strings.HasPrefix(dirname, "/test/") {
-			return
-		}
-	default:
-		// No indexing turned on.
-		return
-	}
-
-	x.fsOpenGate <- true
-	defer func() { <-x.fsOpenGate }()
-
-	// open file
-	f, err := x.c.fs.Open(filename)
-	if err != nil {
-		return
-	}
-
-	x.mu.Lock()
-	defer x.mu.Unlock()
-
-	x.throttle.Throttle()
-
-	x.curPkgExports = make(map[string]SpotKind)
-	file, fast := x.addFile(f, filename, goFile)
-	if file == nil {
-		return // addFile failed
-	}
-
-	if fast != nil {
-		x.indexGoFile(dirname, fi.Name(), file, fast)
-	}
-
-	// update statistics
-	x.stats.Bytes += file.Size()
-	x.stats.Files++
-	x.stats.Lines += file.LineCount()
-}
-
-// indexOptions contains information that affects the contents of an index.
-type indexOptions struct {
-	// Docs provides documentation search results.
-	// It is only consulted if IndexEnabled is true.
-	// The default values is true.
-	Docs bool
-
-	// GoCode provides Go source code search results.
-	// It is only consulted if IndexEnabled is true.
-	// The default values is true.
-	GoCode bool
-
-	// FullText provides search results from all files.
-	// It is only consulted if IndexEnabled is true.
-	// The default values is true.
-	FullText bool
-
-	// MaxResults optionally specifies the maximum results for indexing.
-	// The default is 1000.
-	MaxResults int
-}
-
-// ----------------------------------------------------------------------------
-// Index
-
-type LookupResult struct {
-	Decls  HitList // package-level declarations (with snippets)
-	Others HitList // all other occurrences
-}
-
-type Index struct {
-	fset        *token.FileSet           // file set used during indexing; nil if no textindex
-	suffixes    *suffixarray.Index       // suffixes for concatenated sources; nil if no textindex
-	words       map[string]*LookupResult // maps words to hit lists
-	alts        map[string]*AltWords     // maps canonical(words) to lists of alternative spellings
-	snippets    []*Snippet               // all snippets, indexed by snippet index
-	stats       Statistics
-	importCount map[string]int                 // package path ("net/http") => count
-	packagePath map[string]map[string]bool     // "template" => "text/template" => true
-	exports     map[string]map[string]SpotKind // "net/http" => "ListenAndServe" => FuncDecl
-	idents      map[SpotKind]map[string][]Ident
-	opts        indexOptions
-}
-
-func canonical(w string) string { return strings.ToLower(w) }
-
-// Somewhat arbitrary, but I figure low enough to not hurt disk-based filesystems
-// consuming file descriptors, where some systems have low 256 or 512 limits.
-// Go should have a built-in way to cap fd usage under the ulimit.
-const (
-	maxOpenFiles = 200
-	maxOpenDirs  = 50
-)
-
-func (c *Corpus) throttle() float64 {
-	if c.IndexThrottle <= 0 {
-		return 0.9
-	}
-	if c.IndexThrottle > 1.0 {
-		return 1.0
-	}
-	return c.IndexThrottle
-}
-
-// NewIndex creates a new index for the .go files provided by the corpus.
-func (c *Corpus) NewIndex() *Index {
-	// initialize Indexer
-	// (use some reasonably sized maps to start)
-	x := &Indexer{
-		c:           c,
-		fset:        token.NewFileSet(),
-		fsOpenGate:  make(chan bool, maxOpenFiles),
-		strings:     make(map[string]string),
-		packages:    make(map[Pak]*Pak, 256),
-		words:       make(map[string]*IndexResult, 8192),
-		throttle:    util.NewThrottle(c.throttle(), 100*time.Millisecond), // run at least 0.1s at a time
-		importCount: make(map[string]int),
-		packagePath: make(map[string]map[string]bool),
-		exports:     make(map[string]map[string]SpotKind),
-		idents:      make(map[SpotKind]map[string][]Ident, 4),
-	}
-
-	// index all files in the directories given by dirnames
-	var wg sync.WaitGroup // outstanding ReadDir + visitFile
-	dirGate := make(chan bool, maxOpenDirs)
-	for dirname := range c.fsDirnames() {
-		if c.IndexDirectory != nil && !c.IndexDirectory(dirname) {
-			continue
-		}
-		dirGate <- true
-		wg.Add(1)
-		go func(dirname string) {
-			defer func() { <-dirGate }()
-			defer wg.Done()
-
-			list, err := c.fs.ReadDir(dirname)
-			if err != nil {
-				log.Printf("ReadDir(%q): %v; skipping directory", dirname, err)
-				return // ignore this directory
-			}
-			for _, fi := range list {
-				wg.Add(1)
-				go func(fi os.FileInfo) {
-					defer wg.Done()
-					x.visitFile(dirname, fi)
-				}(fi)
-			}
-		}(dirname)
-	}
-	wg.Wait()
-
-	if !c.IndexFullText {
-		// the file set, the current file, and the sources are
-		// not needed after indexing if no text index is built -
-		// help GC and clear them
-		x.fset = nil
-		x.sources.Reset()
-		x.current = nil // contains reference to fset!
-	}
-
-	// for each word, reduce the RunLists into a LookupResult;
-	// also collect the word with its canonical spelling in a
-	// word list for later computation of alternative spellings
-	words := make(map[string]*LookupResult)
-	var wlist RunList
-	for w, h := range x.words {
-		decls := reduce(h.Decls)
-		others := reduce(h.Others)
-		words[w] = &LookupResult{
-			Decls:  decls,
-			Others: others,
-		}
-		wlist = append(wlist, &wordPair{canonical(w), w})
-		x.throttle.Throttle()
-	}
-	x.stats.Words = len(words)
-
-	// reduce the word list {canonical(w), w} into
-	// a list of AltWords runs {canonical(w), {w}}
-	alist := wlist.reduce(lessWordPair, newAltWords)
-
-	// convert alist into a map of alternative spellings
-	alts := make(map[string]*AltWords)
-	for i := 0; i < len(alist); i++ {
-		a := alist[i].(*AltWords)
-		alts[a.Canon] = a
-	}
-
-	// create text index
-	var suffixes *suffixarray.Index
-	if c.IndexFullText {
-		suffixes = suffixarray.New(x.sources.Bytes())
-	}
-
-	// sort idents by the number of imports of their respective packages
-	for _, idMap := range x.idents {
-		for _, ir := range idMap {
-			sort.Sort(byImportCount{ir, x.importCount})
-		}
-	}
-
-	return &Index{
-		fset:        x.fset,
-		suffixes:    suffixes,
-		words:       words,
-		alts:        alts,
-		snippets:    x.snippets,
-		stats:       x.stats,
-		importCount: x.importCount,
-		packagePath: x.packagePath,
-		exports:     x.exports,
-		idents:      x.idents,
-		opts: indexOptions{
-			Docs:       x.c.IndexDocs,
-			GoCode:     x.c.IndexGoCode,
-			FullText:   x.c.IndexFullText,
-			MaxResults: x.c.MaxResults,
-		},
-	}
-}
-
-var ErrFileIndexVersion = errors.New("file index version out of date")
-
-const fileIndexVersion = 3
-
-// fileIndex is the subset of Index that's gob-encoded for use by
-// Index.Write and Index.Read.
-type fileIndex struct {
-	Version     int
-	Words       map[string]*LookupResult
-	Alts        map[string]*AltWords
-	Snippets    []*Snippet
-	Fulltext    bool
-	Stats       Statistics
-	ImportCount map[string]int
-	PackagePath map[string]map[string]bool
-	Exports     map[string]map[string]SpotKind
-	Idents      map[SpotKind]map[string][]Ident
-	Opts        indexOptions
-}
-
-func (x *fileIndex) Write(w io.Writer) error {
-	return gob.NewEncoder(w).Encode(x)
-}
-
-func (x *fileIndex) Read(r io.Reader) error {
-	return gob.NewDecoder(r).Decode(x)
-}
-
-// WriteTo writes the index x to w.
-func (x *Index) WriteTo(w io.Writer) (n int64, err error) {
-	w = countingWriter{&n, w}
-	fulltext := false
-	if x.suffixes != nil {
-		fulltext = true
-	}
-	fx := fileIndex{
-		Version:     fileIndexVersion,
-		Words:       x.words,
-		Alts:        x.alts,
-		Snippets:    x.snippets,
-		Fulltext:    fulltext,
-		Stats:       x.stats,
-		ImportCount: x.importCount,
-		PackagePath: x.packagePath,
-		Exports:     x.exports,
-		Idents:      x.idents,
-		Opts:        x.opts,
-	}
-	if err := fx.Write(w); err != nil {
-		return 0, err
-	}
-	if fulltext {
-		encode := func(x interface{}) error {
-			return gob.NewEncoder(w).Encode(x)
-		}
-		if err := x.fset.Write(encode); err != nil {
-			return 0, err
-		}
-		if err := x.suffixes.Write(w); err != nil {
-			return 0, err
-		}
-	}
-	return n, nil
-}
-
-// ReadFrom reads the index from r into x; x must not be nil.
-// If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader.
-// If the index is from an old version, the error is ErrFileIndexVersion.
-func (x *Index) ReadFrom(r io.Reader) (n int64, err error) {
-	// We use the ability to read bytes as a plausible surrogate for buffering.
-	if _, ok := r.(io.ByteReader); !ok {
-		r = bufio.NewReader(r)
-	}
-	r = countingReader{&n, r.(byteReader)}
-	var fx fileIndex
-	if err := fx.Read(r); err != nil {
-		return n, err
-	}
-	if fx.Version != fileIndexVersion {
-		return 0, ErrFileIndexVersion
-	}
-	x.words = fx.Words
-	x.alts = fx.Alts
-	x.snippets = fx.Snippets
-	x.stats = fx.Stats
-	x.importCount = fx.ImportCount
-	x.packagePath = fx.PackagePath
-	x.exports = fx.Exports
-	x.idents = fx.Idents
-	x.opts = fx.Opts
-	if fx.Fulltext {
-		x.fset = token.NewFileSet()
-		decode := func(x interface{}) error {
-			return gob.NewDecoder(r).Decode(x)
-		}
-		if err := x.fset.Read(decode); err != nil {
-			return n, err
-		}
-		x.suffixes = new(suffixarray.Index)
-		if err := x.suffixes.Read(r); err != nil {
-			return n, err
-		}
-	}
-	return n, nil
-}
-
-// Stats returns index statistics.
-func (x *Index) Stats() Statistics {
-	return x.stats
-}
-
-// ImportCount returns a map from import paths to how many times they were seen.
-func (x *Index) ImportCount() map[string]int {
-	return x.importCount
-}
-
-// PackagePath returns a map from short package name to a set
-// of full package path names that use that short package name.
-func (x *Index) PackagePath() map[string]map[string]bool {
-	return x.packagePath
-}
-
-// Exports returns a map from full package path to exported
-// symbol name to its type.
-func (x *Index) Exports() map[string]map[string]SpotKind {
-	return x.exports
-}
-
-// Idents returns a map from identifier type to exported
-// symbol name to the list of identifiers matching that name.
-func (x *Index) Idents() map[SpotKind]map[string][]Ident {
-	return x.idents
-}
-
-func (x *Index) lookupWord(w string) (match *LookupResult, alt *AltWords) {
-	match = x.words[w]
-	alt = x.alts[canonical(w)]
-	// remove current spelling from alternatives
-	// (if there is no match, the alternatives do
-	// not contain the current spelling)
-	if match != nil && alt != nil {
-		alt = alt.filter(w)
-	}
-	return
-}
-
-// isIdentifier reports whether s is a Go identifier.
-func isIdentifier(s string) bool {
-	for i, ch := range s {
-		if unicode.IsLetter(ch) || ch == '_' || i > 0 && unicode.IsDigit(ch) {
-			continue
-		}
-		return false
-	}
-	return len(s) > 0
-}
-
-// For a given query, which is either a single identifier or a qualified
-// identifier, Lookup returns a SearchResult containing packages, a LookupResult, a
-// list of alternative spellings, and identifiers, if any. Any and all results
-// may be nil.  If the query syntax is wrong, an error is reported.
-func (x *Index) Lookup(query string) (*SearchResult, error) {
-	ss := strings.Split(query, ".")
-
-	// check query syntax
-	for _, s := range ss {
-		if !isIdentifier(s) {
-			return nil, errors.New("all query parts must be identifiers")
-		}
-	}
-	rslt := &SearchResult{
-		Query:  query,
-		Idents: make(map[SpotKind][]Ident, 5),
-	}
-	// handle simple and qualified identifiers
-	switch len(ss) {
-	case 1:
-		ident := ss[0]
-		rslt.Hit, rslt.Alt = x.lookupWord(ident)
-		if rslt.Hit != nil {
-			// found a match - filter packages with same name
-			// for the list of packages called ident, if any
-			rslt.Pak = rslt.Hit.Others.filter(ident)
-		}
-		for k, v := range x.idents {
-			const rsltLimit = 50
-			ids := byImportCount{v[ident], x.importCount}
-			rslt.Idents[k] = ids.top(rsltLimit)
-		}
-
-	case 2:
-		pakname, ident := ss[0], ss[1]
-		rslt.Hit, rslt.Alt = x.lookupWord(ident)
-		if rslt.Hit != nil {
-			// found a match - filter by package name
-			// (no paks - package names are not qualified)
-			decls := rslt.Hit.Decls.filter(pakname)
-			others := rslt.Hit.Others.filter(pakname)
-			rslt.Hit = &LookupResult{decls, others}
-		}
-		for k, v := range x.idents {
-			ids := byImportCount{v[ident], x.importCount}
-			rslt.Idents[k] = ids.filter(pakname)
-		}
-
-	default:
-		return nil, errors.New("query is not a (qualified) identifier")
-	}
-
-	return rslt, nil
-}
-
-func (x *Index) Snippet(i int) *Snippet {
-	// handle illegal snippet indices gracefully
-	if 0 <= i && i < len(x.snippets) {
-		return x.snippets[i]
-	}
-	return nil
-}
-
-type positionList []struct {
-	filename string
-	line     int
-}
-
-func (list positionList) Len() int           { return len(list) }
-func (list positionList) Less(i, j int) bool { return list[i].filename < list[j].filename }
-func (list positionList) Swap(i, j int)      { list[i], list[j] = list[j], list[i] }
-
-// unique returns the list sorted and with duplicate entries removed
-func unique(list []int) []int {
-	sort.Ints(list)
-	var last int
-	i := 0
-	for _, x := range list {
-		if i == 0 || x != last {
-			last = x
-			list[i] = x
-			i++
-		}
-	}
-	return list[0:i]
-}
-
-// A FileLines value specifies a file and line numbers within that file.
-type FileLines struct {
-	Filename string
-	Lines    []int
-}
-
-// LookupRegexp returns the number of matches and the matches where a regular
-// expression r is found in the full text index. At most n matches are
-// returned (thus found <= n).
-//
-func (x *Index) LookupRegexp(r *regexp.Regexp, n int) (found int, result []FileLines) {
-	if x.suffixes == nil || n <= 0 {
-		return
-	}
-	// n > 0
-
-	var list positionList
-	// FindAllIndex may returns matches that span across file boundaries.
-	// Such matches are unlikely, buf after eliminating them we may end up
-	// with fewer than n matches. If we don't have enough at the end, redo
-	// the search with an increased value n1, but only if FindAllIndex
-	// returned all the requested matches in the first place (if it
-	// returned fewer than that there cannot be more).
-	for n1 := n; found < n; n1 += n - found {
-		found = 0
-		matches := x.suffixes.FindAllIndex(r, n1)
-		// compute files, exclude matches that span file boundaries,
-		// and map offsets to file-local offsets
-		list = make(positionList, len(matches))
-		for _, m := range matches {
-			// by construction, an offset corresponds to the Pos value
-			// for the file set - use it to get the file and line
-			p := token.Pos(m[0])
-			if file := x.fset.File(p); file != nil {
-				if base := file.Base(); base <= m[1] && m[1] <= base+file.Size() {
-					// match [m[0], m[1]) is within the file boundaries
-					list[found].filename = file.Name()
-					list[found].line = file.Line(p)
-					found++
-				}
-			}
-		}
-		if found == n || len(matches) < n1 {
-			// found all matches or there's no chance to find more
-			break
-		}
-	}
-	list = list[0:found]
-	sort.Sort(list) // sort by filename
-
-	// collect matches belonging to the same file
-	var last string
-	var lines []int
-	addLines := func() {
-		if len(lines) > 0 {
-			// remove duplicate lines
-			result = append(result, FileLines{last, unique(lines)})
-			lines = nil
-		}
-	}
-	for _, m := range list {
-		if m.filename != last {
-			addLines()
-			last = m.filename
-		}
-		lines = append(lines, m.line)
-	}
-	addLines()
-
-	return
-}
-
-// InvalidateIndex should be called whenever any of the file systems
-// under godoc's observation change so that the indexer is kicked on.
-func (c *Corpus) invalidateIndex() {
-	c.fsModified.Set(nil)
-	c.refreshMetadata()
-}
-
-// feedDirnames feeds the directory names of all directories
-// under the file system given by root to channel c.
-//
-func (c *Corpus) feedDirnames(ch chan<- string) {
-	if dir, _ := c.fsTree.Get(); dir != nil {
-		for d := range dir.(*Directory).iter(false) {
-			ch <- d.Path
-		}
-	}
-}
-
-// fsDirnames() returns a channel sending all directory names
-// of all the file systems under godoc's observation.
-//
-func (c *Corpus) fsDirnames() <-chan string {
-	ch := make(chan string, 256) // buffered for fewer context switches
-	go func() {
-		c.feedDirnames(ch)
-		close(ch)
-	}()
-	return ch
-}
-
-// CompatibleWith reports whether the Index x is compatible with the corpus
-// indexing options set in c.
-func (x *Index) CompatibleWith(c *Corpus) bool {
-	return x.opts.Docs == c.IndexDocs &&
-		x.opts.GoCode == c.IndexGoCode &&
-		x.opts.FullText == c.IndexFullText &&
-		x.opts.MaxResults == c.MaxResults
-}
-
-func (c *Corpus) readIndex(filenames string) error {
-	matches, err := filepath.Glob(filenames)
-	if err != nil {
-		return err
-	} else if matches == nil {
-		return fmt.Errorf("no index files match %q", filenames)
-	}
-	sort.Strings(matches) // make sure files are in the right order
-	files := make([]io.Reader, 0, len(matches))
-	for _, filename := range matches {
-		f, err := os.Open(filename)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-		files = append(files, f)
-	}
-	return c.ReadIndexFrom(io.MultiReader(files...))
-}
-
-// ReadIndexFrom sets the current index from the serialized version found in r.
-func (c *Corpus) ReadIndexFrom(r io.Reader) error {
-	x := new(Index)
-	if _, err := x.ReadFrom(r); err != nil {
-		return err
-	}
-	if !x.CompatibleWith(c) {
-		return fmt.Errorf("index file options are incompatible: %v", x.opts)
-	}
-	c.searchIndex.Set(x)
-	return nil
-}
-
-func (c *Corpus) UpdateIndex() {
-	if c.Verbose {
-		log.Printf("updating index...")
-	}
-	start := time.Now()
-	index := c.NewIndex()
-	stop := time.Now()
-	c.searchIndex.Set(index)
-	if c.Verbose {
-		secs := stop.Sub(start).Seconds()
-		stats := index.Stats()
-		log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)",
-			secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots)
-	}
-	memstats := new(runtime.MemStats)
-	runtime.ReadMemStats(memstats)
-	if c.Verbose {
-		log.Printf("before GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
-	}
-	runtime.GC()
-	runtime.ReadMemStats(memstats)
-	if c.Verbose {
-		log.Printf("after  GC: bytes = %d footprint = %d", memstats.HeapAlloc, memstats.Sys)
-	}
-}
-
-// RunIndexer runs forever, indexing.
-func (c *Corpus) RunIndexer() {
-	// initialize the index from disk if possible
-	if c.IndexFiles != "" {
-		c.initFSTree()
-		if err := c.readIndex(c.IndexFiles); err != nil {
-			log.Printf("error reading index from file %s: %v", c.IndexFiles, err)
-		}
-		return
-	}
-
-	// Repeatedly update the package directory tree and index.
-	for {
-		c.initFSTree()
-		c.UpdateIndex()
-		if c.IndexInterval < 0 {
-			return
-		}
-		delay := 5 * time.Minute // by default, reindex every 5 minutes
-		if c.IndexInterval > 0 {
-			delay = c.IndexInterval
-		}
-		time.Sleep(delay)
-	}
-}
-
-type countingWriter struct {
-	n *int64
-	w io.Writer
-}
-
-func (c countingWriter) Write(p []byte) (n int, err error) {
-	n, err = c.w.Write(p)
-	*c.n += int64(n)
-	return
-}
-
-type byteReader interface {
-	io.Reader
-	io.ByteReader
-}
-
-type countingReader struct {
-	n *int64
-	r byteReader
-}
-
-func (c countingReader) Read(p []byte) (n int, err error) {
-	n, err = c.r.Read(p)
-	*c.n += int64(n)
-	return
-}
-
-func (c countingReader) ReadByte() (b byte, err error) {
-	b, err = c.r.ReadByte()
-	*c.n += 1
-	return
-}
diff --git a/cmd/golangorg/godoc/index_test.go b/cmd/golangorg/godoc/index_test.go
deleted file mode 100644
index 5e1d64b..0000000
--- a/cmd/golangorg/godoc/index_test.go
+++ /dev/null
@@ -1,323 +0,0 @@
-// 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.
-
-package godoc
-
-import (
-	"bytes"
-	"reflect"
-	"sort"
-	"strings"
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/mapfs"
-)
-
-func newCorpus(t *testing.T) *Corpus {
-	c := NewCorpus(mapfs.New(map[string]string{
-		"src/foo/foo.go": `// Package foo is an example.
-package foo
-
-import "bar"
-
-const Pi = 3.1415
-
-var Foos []Foo
-
-// Foo is stuff.
-type Foo struct{}
-
-func New() *Foo {
-   return new(Foo)
-}
-`,
-		"src/bar/bar.go": `// Package bar is another example to test races.
-package bar
-`,
-		"src/other/bar/bar.go": `// Package bar is another bar package.
-package bar
-func X() {}
-`,
-		"src/skip/skip.go": `// Package skip should be skipped.
-package skip
-func Skip() {}
-`,
-		"src/bar/readme.txt": `Whitelisted text file.
-`,
-		"src/bar/baz.zzz": `Text file not whitelisted.
-`,
-	}))
-	c.IndexEnabled = true
-	c.IndexDirectory = func(dir string) bool {
-		return !strings.Contains(dir, "skip")
-	}
-
-	if err := c.Init(); err != nil {
-		t.Fatal(err)
-	}
-	return c
-}
-
-func TestIndex(t *testing.T) {
-	for _, docs := range []bool{true, false} {
-		for _, goCode := range []bool{true, false} {
-			for _, fullText := range []bool{true, false} {
-				c := newCorpus(t)
-				c.IndexDocs = docs
-				c.IndexGoCode = goCode
-				c.IndexFullText = fullText
-				c.UpdateIndex()
-				ix, _ := c.CurrentIndex()
-				if ix == nil {
-					t.Fatal("no index")
-				}
-				t.Logf("docs, goCode, fullText = %v,%v,%v", docs, goCode, fullText)
-				testIndex(t, c, ix)
-			}
-		}
-	}
-}
-
-func TestIndexWriteRead(t *testing.T) {
-	type key struct {
-		docs, goCode, fullText bool
-	}
-	type val struct {
-		buf *bytes.Buffer
-		c   *Corpus
-	}
-	m := map[key]val{}
-
-	for _, docs := range []bool{true, false} {
-		for _, goCode := range []bool{true, false} {
-			for _, fullText := range []bool{true, false} {
-				k := key{docs, goCode, fullText}
-				c := newCorpus(t)
-				c.IndexDocs = docs
-				c.IndexGoCode = goCode
-				c.IndexFullText = fullText
-				c.UpdateIndex()
-				ix, _ := c.CurrentIndex()
-				if ix == nil {
-					t.Fatal("no index")
-				}
-				var buf bytes.Buffer
-				nw, err := ix.WriteTo(&buf)
-				if err != nil {
-					t.Fatalf("Index.WriteTo: %v", err)
-				}
-				m[k] = val{bytes.NewBuffer(buf.Bytes()), c}
-				ix2 := new(Index)
-				nr, err := ix2.ReadFrom(&buf)
-				if err != nil {
-					t.Fatalf("Index.ReadFrom: %v", err)
-				}
-				if nr != nw {
-					t.Errorf("Wrote %d bytes to index but read %d", nw, nr)
-				}
-				testIndex(t, c, ix)
-			}
-		}
-	}
-	// Test CompatibleWith
-	for k1, v1 := range m {
-		ix := new(Index)
-		if _, err := ix.ReadFrom(v1.buf); err != nil {
-			t.Fatalf("Index.ReadFrom: %v", err)
-		}
-		for k2, v2 := range m {
-			if got, want := ix.CompatibleWith(v2.c), k1 == k2; got != want {
-				t.Errorf("CompatibleWith = %v; want %v for %v, %v", got, want, k1, k2)
-			}
-		}
-	}
-}
-
-func testIndex(t *testing.T, c *Corpus, ix *Index) {
-	if _, ok := ix.words["Skip"]; ok {
-		t.Errorf("the word Skip was found; expected it to be skipped")
-	}
-	checkStats(t, c, ix)
-	checkImportCount(t, c, ix)
-	checkPackagePath(t, c, ix)
-	checkExports(t, c, ix)
-	checkIdents(t, c, ix)
-}
-
-// checkStats checks the Index's statistics.
-// Some statistics are only set when we're indexing Go code.
-func checkStats(t *testing.T, c *Corpus, ix *Index) {
-	want := Statistics{}
-	if c.IndexFullText {
-		want.Bytes = 314
-		want.Files = 4
-		want.Lines = 21
-	} else if c.IndexDocs || c.IndexGoCode {
-		want.Bytes = 291
-		want.Files = 3
-		want.Lines = 20
-	}
-	if c.IndexGoCode {
-		want.Words = 8
-		want.Spots = 12
-	}
-	if got := ix.Stats(); !reflect.DeepEqual(got, want) {
-		t.Errorf("Stats = %#v; want %#v", got, want)
-	}
-}
-
-// checkImportCount checks the Index's import count map.
-// It is only set when we're indexing Go code.
-func checkImportCount(t *testing.T, c *Corpus, ix *Index) {
-	want := map[string]int{}
-	if c.IndexGoCode {
-		want = map[string]int{
-			"bar": 1,
-		}
-	}
-	if got := ix.ImportCount(); !reflect.DeepEqual(got, want) {
-		t.Errorf("ImportCount = %v; want %v", got, want)
-	}
-}
-
-// checkPackagePath checks the Index's package path map.
-// It is set if at least one of the indexing options is enabled.
-func checkPackagePath(t *testing.T, c *Corpus, ix *Index) {
-	want := map[string]map[string]bool{}
-	if c.IndexDocs || c.IndexGoCode || c.IndexFullText {
-		want = map[string]map[string]bool{
-			"foo": {
-				"foo": true,
-			},
-			"bar": {
-				"bar":       true,
-				"other/bar": true,
-			},
-		}
-	}
-	if got := ix.PackagePath(); !reflect.DeepEqual(got, want) {
-		t.Errorf("PackagePath = %v; want %v", got, want)
-	}
-}
-
-// checkExports checks the Index's exports map.
-// It is only set when we're indexing Go code.
-func checkExports(t *testing.T, c *Corpus, ix *Index) {
-	want := map[string]map[string]SpotKind{}
-	if c.IndexGoCode {
-		want = map[string]map[string]SpotKind{
-			"foo": {
-				"Pi":   ConstDecl,
-				"Foos": VarDecl,
-				"Foo":  TypeDecl,
-				"New":  FuncDecl,
-			},
-			"other/bar": {
-				"X": FuncDecl,
-			},
-		}
-	}
-	if got := ix.Exports(); !reflect.DeepEqual(got, want) {
-		t.Errorf("Exports = %v; want %v", got, want)
-	}
-}
-
-// checkIdents checks the Index's indents map.
-// It is only set when we're indexing documentation.
-func checkIdents(t *testing.T, c *Corpus, ix *Index) {
-	want := map[SpotKind]map[string][]Ident{}
-	if c.IndexDocs {
-		want = map[SpotKind]map[string][]Ident{
-			PackageClause: {
-				"bar": {
-					{"bar", "bar", "bar", "Package bar is another example to test races."},
-					{"other/bar", "bar", "bar", "Package bar is another bar package."},
-				},
-				"foo":   {{"foo", "foo", "foo", "Package foo is an example."}},
-				"other": {{"other/bar", "bar", "bar", "Package bar is another bar package."}},
-			},
-			ConstDecl: {
-				"Pi": {{"foo", "foo", "Pi", ""}},
-			},
-			VarDecl: {
-				"Foos": {{"foo", "foo", "Foos", ""}},
-			},
-			TypeDecl: {
-				"Foo": {{"foo", "foo", "Foo", "Foo is stuff."}},
-			},
-			FuncDecl: {
-				"New": {{"foo", "foo", "New", ""}},
-				"X":   {{"other/bar", "bar", "X", ""}},
-			},
-		}
-	}
-	if got := ix.Idents(); !reflect.DeepEqual(got, want) {
-		t.Errorf("Idents = %v; want %v", got, want)
-	}
-}
-
-func TestIdentResultSort(t *testing.T) {
-	ic := map[string]int{
-		"/a/b/pkg1": 10,
-		"/a/b/pkg2": 2,
-		"/b/d/pkg3": 20,
-	}
-	for _, tc := range []struct {
-		ir  []Ident
-		exp []Ident
-	}{
-		{
-			ir: []Ident{
-				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
-				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
-				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
-			},
-			exp: []Ident{
-				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
-				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
-				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
-			},
-		},
-		{
-			ir: []Ident{
-				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
-				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
-			},
-			exp: []Ident{
-				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
-				{"/a/a/pkg1", "pkg1", "MyFunc1", ""},
-			},
-		},
-	} {
-		if sort.Sort(byImportCount{tc.ir, ic}); !reflect.DeepEqual(tc.ir, tc.exp) {
-			t.Errorf("got: %v, want %v", tc.ir, tc.exp)
-		}
-	}
-}
-
-func TestIdentFilter(t *testing.T) {
-	ic := map[string]int{}
-	for _, tc := range []struct {
-		ir  []Ident
-		pak string
-		exp []Ident
-	}{
-		{
-			ir: []Ident{
-				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
-				{"/b/d/pkg3", "pkg3", "MyFunc3", ""},
-				{"/a/b/pkg1", "pkg1", "MyFunc1", ""},
-			},
-			pak: "pkg2",
-			exp: []Ident{
-				{"/a/b/pkg2", "pkg2", "MyFunc2", ""},
-			},
-		},
-	} {
-		res := byImportCount{tc.ir, ic}.filter(tc.pak)
-		if !reflect.DeepEqual(res, tc.exp) {
-			t.Errorf("got: %v, want %v", res, tc.exp)
-		}
-	}
-}
diff --git a/cmd/golangorg/godoc/linkify.go b/cmd/golangorg/godoc/linkify.go
deleted file mode 100644
index e4add22..0000000
--- a/cmd/golangorg/godoc/linkify.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// 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/doc"
-	"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.isVal {
-					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 {
-	path, name string // package path, identifier name
-	isVal      bool   // identifier is defined in a const or var declaration
-}
-
-// 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) (links []link) {
-	// linkMap tracks link information for each ast.Ident node. Entries may
-	// be created out of source order (for example, when we visit a parent
-	// definition node). These links are appended to the returned slice when
-	// their ast.Ident nodes are visited.
-	linkMap := make(map[*ast.Ident]link)
-
-	ast.Inspect(node, func(node ast.Node) bool {
-		switch n := node.(type) {
-		case *ast.Field:
-			for _, n := range n.Names {
-				linkMap[n] = link{}
-			}
-		case *ast.ImportSpec:
-			if name := n.Name; name != nil {
-				linkMap[name] = link{}
-			}
-		case *ast.ValueSpec:
-			for _, n := range n.Names {
-				linkMap[n] = link{name: n.Name, isVal: true}
-			}
-		case *ast.FuncDecl:
-			linkMap[n.Name] = link{}
-		case *ast.TypeSpec:
-			linkMap[n.Name] = link{}
-		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 {
-						linkMap[n] = link{isVal: true}
-					}
-				}
-			}
-		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 {
-				// Create links only if x is 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.
-							linkMap[x] = link{path: path}
-							linkMap[n.Sel] = link{path: path, name: n.Sel.Name}
-						}
-					}
-				}
-			}
-		case *ast.CompositeLit:
-			// Detect field names within composite literals. These links should
-			// be prefixed by the type name.
-			fieldPath := ""
-			prefix := ""
-			switch typ := n.Type.(type) {
-			case *ast.Ident:
-				prefix = typ.Name + "."
-			case *ast.SelectorExpr:
-				if x, _ := typ.X.(*ast.Ident); x != nil {
-					// Create links only if x is 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.
-								linkMap[x] = link{path: path}
-								linkMap[typ.Sel] = link{path: path, name: typ.Sel.Name}
-								fieldPath = path
-								prefix = typ.Sel.Name + "."
-							}
-						}
-					}
-				}
-			}
-			for _, e := range n.Elts {
-				if kv, ok := e.(*ast.KeyValueExpr); ok {
-					if k, ok := kv.Key.(*ast.Ident); ok {
-						// Note: there is some syntactic ambiguity here. We cannot determine
-						// if this is a struct literal or a map literal without type
-						// information. We assume struct literal.
-						name := prefix + k.Name
-						linkMap[k] = link{path: fieldPath, name: name}
-					}
-				}
-			}
-		case *ast.Ident:
-			if l, ok := linkMap[n]; ok {
-				links = append(links, l)
-			} else {
-				l := link{name: n.Name}
-				if n.Obj == nil && doc.IsPredeclared(n.Name) {
-					l.path = builtinPkgPath
-				}
-				links = append(links, l)
-			}
-		}
-		return true
-	})
-	return
-}
diff --git a/cmd/golangorg/godoc/meta.go b/cmd/golangorg/godoc/meta.go
deleted file mode 100644
index 4fa67a7..0000000
--- a/cmd/golangorg/godoc/meta.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2009 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.
-
-package godoc
-
-import (
-	"bytes"
-	"encoding/json"
-	"log"
-	pathpkg "path"
-	"strings"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-var (
-	doctype   = []byte("<!DOCTYPE ")
-	jsonStart = []byte("<!--{")
-	jsonEnd   = []byte("}-->")
-)
-
-// ----------------------------------------------------------------------------
-// Documentation Metadata
-
-// TODO(adg): why are some exported and some aren't? -brad
-type Metadata struct {
-	Title    string
-	Subtitle string
-	Template bool   // execute as template
-	Path     string // canonical path for this page
-	filePath string // filesystem path relative to goroot
-}
-
-func (m *Metadata) FilePath() string { return m.filePath }
-
-// extractMetadata extracts the Metadata from a byte slice.
-// It returns the Metadata value and the remaining data.
-// If no metadata is present the original byte slice is returned.
-//
-func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) {
-	tail = b
-	if !bytes.HasPrefix(b, jsonStart) {
-		return
-	}
-	end := bytes.Index(b, jsonEnd)
-	if end < 0 {
-		return
-	}
-	b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing }
-	if err = json.Unmarshal(b, &meta); err != nil {
-		return
-	}
-	tail = tail[end+len(jsonEnd):]
-	return
-}
-
-// UpdateMetadata scans $GOROOT/doc for HTML files, reads their metadata,
-// and updates the DocMetadata map.
-func (c *Corpus) updateMetadata() {
-	metadata := make(map[string]*Metadata)
-	var scan func(string) // scan is recursive
-	scan = func(dir string) {
-		fis, err := c.fs.ReadDir(dir)
-		if err != nil {
-			log.Println("updateMetadata:", err)
-			return
-		}
-		for _, fi := range fis {
-			name := pathpkg.Join(dir, fi.Name())
-			if fi.IsDir() {
-				scan(name) // recurse
-				continue
-			}
-			if !strings.HasSuffix(name, ".html") {
-				continue
-			}
-			// Extract metadata from the file.
-			b, err := vfs.ReadFile(c.fs, name)
-			if err != nil {
-				log.Printf("updateMetadata %s: %v", name, err)
-				continue
-			}
-			meta, _, err := extractMetadata(b)
-			if err != nil {
-				log.Printf("updateMetadata: %s: %v", name, err)
-				continue
-			}
-			// Store relative filesystem path in Metadata.
-			meta.filePath = name
-			if meta.Path == "" {
-				// If no Path, canonical path is actual path.
-				meta.Path = meta.filePath
-			}
-			// Store under both paths.
-			metadata[meta.Path] = &meta
-			metadata[meta.filePath] = &meta
-		}
-	}
-	scan("/doc")
-	c.docMetadata.Set(metadata)
-}
-
-// MetadataFor returns the *Metadata for a given relative path or nil if none
-// exists.
-//
-func (c *Corpus) MetadataFor(relpath string) *Metadata {
-	if m, _ := c.docMetadata.Get(); m != nil {
-		meta := m.(map[string]*Metadata)
-		// If metadata for this relpath exists, return it.
-		if p := meta[relpath]; p != nil {
-			return p
-		}
-		// Try with or without trailing slash.
-		if strings.HasSuffix(relpath, "/") {
-			relpath = relpath[:len(relpath)-1]
-		} else {
-			relpath = relpath + "/"
-		}
-		return meta[relpath]
-	}
-	return nil
-}
-
-// refreshMetadata sends a signal to update DocMetadata. If a refresh is in
-// progress the metadata will be refreshed again afterward.
-//
-func (c *Corpus) refreshMetadata() {
-	select {
-	case c.refreshMetadataSignal <- true:
-	default:
-	}
-}
-
-// RefreshMetadataLoop runs forever, updating DocMetadata when the underlying
-// file system changes. It should be launched in a goroutine.
-func (c *Corpus) refreshMetadataLoop() {
-	for {
-		<-c.refreshMetadataSignal
-		c.updateMetadata()
-		time.Sleep(10 * time.Second) // at most once every 10 seconds
-	}
-}
diff --git a/cmd/golangorg/godoc/page.go b/cmd/golangorg/godoc/page.go
deleted file mode 100644
index 719789e..0000000
--- a/cmd/golangorg/godoc/page.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2009 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.
-
-package godoc
-
-import (
-	"net/http"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
-
-	"golang.org/x/website/cmd/golangorg/godoc/env"
-)
-
-// Page describes the contents of the top-level godoc webpage.
-type Page struct {
-	Title    string
-	Tabtitle string
-	Subtitle string
-	SrcPath  string
-	Query    string
-	Body     []byte
-	GoogleCN bool // page is being served from golang.google.cn
-	TreeView bool // page needs to contain treeview related js and css
-
-	// filled in by ServePage
-	SearchBox       bool
-	Playground      bool
-	Version         string
-	GoogleAnalytics string
-}
-
-func (p *Presentation) ServePage(w http.ResponseWriter, page Page) {
-	if page.Tabtitle == "" {
-		page.Tabtitle = page.Title
-	}
-	page.SearchBox = p.Corpus.IndexEnabled
-	page.Playground = p.ShowPlayground
-	page.Version = runtime.Version()
-	page.GoogleAnalytics = p.GoogleAnalytics
-	applyTemplateToResponseWriter(w, p.GodocHTML, page)
-}
-
-func (p *Presentation) ServeError(w http.ResponseWriter, r *http.Request, relpath string, err error) {
-	w.WriteHeader(http.StatusNotFound)
-	if perr, ok := err.(*os.PathError); ok {
-		rel, err := filepath.Rel(runtime.GOROOT(), perr.Path)
-		if err != nil {
-			perr.Path = "REDACTED"
-		} else {
-			perr.Path = filepath.Join("$GOROOT", rel)
-		}
-	}
-	p.ServePage(w, Page{
-		Title:           "File " + relpath,
-		Subtitle:        relpath,
-		Body:            applyTemplate(p.ErrorHTML, "errorHTML", err),
-		GoogleCN:        googleCN(r),
-		GoogleAnalytics: p.GoogleAnalytics,
-	})
-}
-
-func googleCN(r *http.Request) bool {
-	if r.FormValue("googlecn") != "" {
-		return true
-	}
-	if !env.IsProd() {
-		return false
-	}
-	if strings.HasSuffix(r.Host, ".cn") {
-		return true
-	}
-	switch r.Header.Get("X-AppEngine-Country") {
-	case "", "ZZ", "CN":
-		return true
-	}
-	return false
-}
diff --git a/cmd/golangorg/godoc/parser.go b/cmd/golangorg/godoc/parser.go
deleted file mode 100644
index 6a00b3b..0000000
--- a/cmd/golangorg/godoc/parser.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2011 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 contains support functions for parsing .go files
-// accessed via godoc's file system fs.
-
-package godoc
-
-import (
-	"bytes"
-	"go/ast"
-	"go/parser"
-	"go/token"
-	pathpkg "path"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-var linePrefix = []byte("//line ")
-
-// This function replaces source lines starting with "//line " with a blank line.
-// It does this irrespective of whether the line is truly a line comment or not;
-// e.g., the line may be inside a string, or a /*-style comment; however that is
-// rather unlikely (proper testing would require a full Go scan which we want to
-// avoid for performance).
-func replaceLinePrefixCommentsWithBlankLine(src []byte) {
-	for {
-		i := bytes.Index(src, linePrefix)
-		if i < 0 {
-			break // we're done
-		}
-		// 0 <= i && i+len(linePrefix) <= len(src)
-		if i == 0 || src[i-1] == '\n' {
-			// at beginning of line: blank out line
-			for i < len(src) && src[i] != '\n' {
-				src[i] = ' '
-				i++
-			}
-		} else {
-			// not at beginning of line: skip over prefix
-			i += len(linePrefix)
-		}
-		// i <= len(src)
-		src = src[i:]
-	}
-}
-
-func (c *Corpus) parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) {
-	src, err := vfs.ReadFile(c.fs, filename)
-	if err != nil {
-		return nil, err
-	}
-
-	// Temporary ad-hoc fix for issue 5247.
-	// TODO(gri) Remove this in favor of a better fix, eventually (see issue 7702).
-	replaceLinePrefixCommentsWithBlankLine(src)
-
-	return parser.ParseFile(fset, filename, src, mode)
-}
-
-func (c *Corpus) parseFiles(fset *token.FileSet, relpath string, abspath string, localnames []string) (map[string]*ast.File, error) {
-	files := make(map[string]*ast.File)
-	for _, f := range localnames {
-		absname := pathpkg.Join(abspath, f)
-		file, err := c.parseFile(fset, absname, parser.ParseComments)
-		if err != nil {
-			return nil, err
-		}
-		files[pathpkg.Join(relpath, f)] = file
-	}
-
-	return files, nil
-}
diff --git a/cmd/golangorg/godoc/pres.go b/cmd/golangorg/godoc/pres.go
deleted file mode 100644
index a5c7fa5..0000000
--- a/cmd/golangorg/godoc/pres.go
+++ /dev/null
@@ -1,169 +0,0 @@
-// 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.
-
-package godoc
-
-import (
-	"net/http"
-	"regexp"
-	"sync"
-	"text/template"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/httpfs"
-)
-
-// SearchResultFunc functions return an HTML body for displaying search results.
-type SearchResultFunc func(p *Presentation, result SearchResult) []byte
-
-// Presentation generates output from a corpus.
-type Presentation struct {
-	Corpus *Corpus
-
-	mux        *http.ServeMux
-	fileServer http.Handler
-	cmdHandler handlerServer
-	pkgHandler handlerServer
-
-	CallGraphHTML,
-	DirlistHTML,
-	ErrorHTML,
-	ExampleHTML,
-	GodocHTML,
-	ImplementsHTML,
-	MethodSetHTML,
-	PackageHTML,
-	PackageRootHTML,
-	SearchHTML,
-	SearchDocHTML,
-	SearchCodeHTML,
-	SearchTxtHTML,
-	SearchDescXML *template.Template
-
-	// TabWidth optionally specifies the tab width.
-	TabWidth int
-
-	ShowTimestamps bool
-	ShowPlayground bool
-	DeclLinks      bool
-
-	// SrcMode outputs source code instead of documentation in command-line mode.
-	SrcMode bool
-	// HTMLMode outputs HTML instead of plain text in command-line mode.
-	HTMLMode bool
-	// AllMode includes unexported identifiers in the output in command-line mode.
-	AllMode bool
-
-	// NotesRx optionally specifies a regexp to match
-	// notes to render in the output.
-	NotesRx *regexp.Regexp
-
-	// AdjustPageInfoMode optionally specifies a function to
-	// modify the PageInfoMode of a request. The default chosen
-	// value is provided.
-	AdjustPageInfoMode func(req *http.Request, mode PageInfoMode) PageInfoMode
-
-	// URLForSrc optionally specifies a function that takes a source file and
-	// returns a URL for it.
-	// The source file argument has the form /src/<path>/<filename>.
-	URLForSrc func(src string) string
-
-	// URLForSrcPos optionally specifies a function to create a URL given a
-	// source file, a line from the source file (1-based), and low & high offset
-	// positions (0-based, bytes from beginning of file). Ideally, the returned
-	// URL will be for the specified line of the file, while the high & low
-	// positions will be used to highlight a section of the file.
-	// The source file argument has the form /src/<path>/<filename>.
-	URLForSrcPos func(src string, line, low, high int) string
-
-	// URLForSrcQuery optionally specifies a function to create a URL given a
-	// source file, a query string, and a line from the source file (1-based).
-	// The source file argument has the form /src/<path>/<filename>.
-	// The query argument will be escaped for the purposes of embedding in a URL
-	// query parameter.
-	// Ideally, the returned URL will be for the specified line of the file with
-	// the query string highlighted.
-	URLForSrcQuery func(src, query string, line int) string
-
-	// SearchResults optionally specifies a list of functions returning an HTML
-	// body for displaying search results.
-	SearchResults []SearchResultFunc
-
-	// GoogleAnalytics optionally adds Google Analytics via the provided
-	// tracking ID to each page.
-	GoogleAnalytics string
-
-	initFuncMapOnce sync.Once
-	funcMap         template.FuncMap
-	templateFuncs   template.FuncMap
-}
-
-// NewPresentation returns a new Presentation from a corpus.
-// It sets SearchResults to:
-// [SearchResultDoc SearchResultCode SearchResultTxt].
-func NewPresentation(c *Corpus) *Presentation {
-	if c == nil {
-		panic("nil Corpus")
-	}
-	p := &Presentation{
-		Corpus:     c,
-		mux:        http.NewServeMux(),
-		fileServer: http.FileServer(httpfs.New(c.fs)),
-
-		TabWidth:  4,
-		DeclLinks: true,
-		SearchResults: []SearchResultFunc{
-			(*Presentation).SearchResultDoc,
-			(*Presentation).SearchResultCode,
-			(*Presentation).SearchResultTxt,
-		},
-	}
-	p.cmdHandler = handlerServer{
-		p:       p,
-		c:       c,
-		pattern: "/cmd/",
-		fsRoot:  "/src",
-	}
-	p.pkgHandler = handlerServer{
-		p:           p,
-		c:           c,
-		pattern:     "/pkg/",
-		stripPrefix: "pkg/",
-		fsRoot:      "/src",
-		exclude:     []string{"/src/cmd"},
-	}
-	p.cmdHandler.registerWithMux(p.mux)
-	p.pkgHandler.registerWithMux(p.mux)
-	p.mux.HandleFunc("/", p.ServeFile)
-	p.mux.HandleFunc("/search", p.HandleSearch)
-	p.mux.HandleFunc("/opensearch.xml", p.serveSearchDesc)
-	return p
-}
-
-func (p *Presentation) FileServer() http.Handler {
-	return p.fileServer
-}
-
-func (p *Presentation) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	p.mux.ServeHTTP(w, r)
-}
-
-func (p *Presentation) PkgFSRoot() string {
-	return p.pkgHandler.fsRoot
-}
-
-func (p *Presentation) CmdFSRoot() string {
-	return p.cmdHandler.fsRoot
-}
-
-// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
-// but this doesn't feel right.
-func (p *Presentation) GetPkgPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
-	return p.pkgHandler.GetPageInfo(abspath, relpath, mode, "", "")
-}
-
-// TODO(bradfitz): move this to be a method on Corpus. Just moving code around for now,
-// but this doesn't feel right.
-func (p *Presentation) GetCmdPageInfo(abspath, relpath string, mode PageInfoMode) *PageInfo {
-	return p.cmdHandler.GetPageInfo(abspath, relpath, mode, "", "")
-}
diff --git a/cmd/golangorg/godoc/proxy/proxy.go b/cmd/golangorg/godoc/proxy/proxy.go
deleted file mode 100644
index faba509..0000000
--- a/cmd/golangorg/godoc/proxy/proxy.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2015 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.
-
-// Package proxy proxies requests to the playground's compile and share handlers.
-// It is designed to run only on the instance of godoc that serves golang.org.
-package proxy
-
-import (
-	"bytes"
-	"context"
-	"encoding/json"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"strings"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/env"
-)
-
-const playgroundURL = "https://play.golang.org"
-
-type Request struct {
-	Body string
-}
-
-type Response struct {
-	Errors string
-	Events []Event
-}
-
-type Event struct {
-	Message string
-	Kind    string        // "stdout" or "stderr"
-	Delay   time.Duration // time to wait before printing Message
-}
-
-const expires = 7 * 24 * time.Hour // 1 week
-var cacheControlHeader = fmt.Sprintf("public, max-age=%d", int(expires.Seconds()))
-
-func RegisterHandlers(mux *http.ServeMux) {
-	mux.HandleFunc("/compile", compile)
-	mux.HandleFunc("/share", share)
-}
-
-func compile(w http.ResponseWriter, r *http.Request) {
-	if r.Method != "POST" {
-		http.Error(w, "I only answer to POST requests.", http.StatusMethodNotAllowed)
-		return
-	}
-
-	ctx := r.Context()
-
-	body := r.FormValue("body")
-	res := &Response{}
-	req := &Request{Body: body}
-	if err := makeCompileRequest(ctx, req, res); err != nil {
-		log.Printf("ERROR compile error: %v", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	var out interface{}
-	switch r.FormValue("version") {
-	case "2":
-		out = res
-	default: // "1"
-		out = struct {
-			CompileErrors string `json:"compile_errors"`
-			Output        string `json:"output"`
-		}{res.Errors, flatten(res.Events)}
-	}
-	b, err := json.Marshal(out)
-	if err != nil {
-		log.Printf("ERROR encoding response: %v", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-
-	expiresTime := time.Now().Add(expires).UTC()
-	w.Header().Set("Expires", expiresTime.Format(time.RFC1123))
-	w.Header().Set("Cache-Control", cacheControlHeader)
-	w.Write(b)
-}
-
-// makePlaygroundRequest sends the given Request to the playground compile
-// endpoint and stores the response in the given Response.
-func makeCompileRequest(ctx context.Context, req *Request, res *Response) error {
-	reqJ, err := json.Marshal(req)
-	if err != nil {
-		return fmt.Errorf("marshalling request: %v", err)
-	}
-	hReq, _ := http.NewRequest("POST", playgroundURL+"/compile", bytes.NewReader(reqJ))
-	hReq.Header.Set("Content-Type", "application/json")
-	hReq = hReq.WithContext(ctx)
-
-	r, err := http.DefaultClient.Do(hReq)
-	if err != nil {
-		return fmt.Errorf("making request: %v", err)
-	}
-	defer r.Body.Close()
-
-	if r.StatusCode != http.StatusOK {
-		b, _ := ioutil.ReadAll(r.Body)
-		return fmt.Errorf("bad status: %v body:\n%s", r.Status, b)
-	}
-
-	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
-		return fmt.Errorf("unmarshalling response: %v", err)
-	}
-	return nil
-}
-
-// flatten takes a sequence of Events and returns their contents, concatenated.
-func flatten(seq []Event) string {
-	var buf bytes.Buffer
-	for _, e := range seq {
-		buf.WriteString(e.Message)
-	}
-	return buf.String()
-}
-
-func share(w http.ResponseWriter, r *http.Request) {
-	if googleCN(r) {
-		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
-		return
-	}
-
-	// HACK(cbro): use a simple proxy rather than httputil.ReverseProxy because of Issue #28168.
-	// TODO: investigate using ReverseProxy with a Director, unsetting whatever's necessary to make that work.
-	req, _ := http.NewRequest("POST", playgroundURL+"/share", r.Body)
-	req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
-	req = req.WithContext(r.Context())
-	resp, err := http.DefaultClient.Do(req)
-	if err != nil {
-		log.Printf("ERROR share error: %v", err)
-		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
-		return
-	}
-	copyHeader := func(k string) {
-		if v := resp.Header.Get(k); v != "" {
-			w.Header().Set(k, v)
-		}
-	}
-	copyHeader("Content-Type")
-	copyHeader("Content-Length")
-	defer resp.Body.Close()
-	w.WriteHeader(resp.StatusCode)
-	io.Copy(w, resp.Body)
-}
-
-func googleCN(r *http.Request) bool {
-	if r.FormValue("googlecn") != "" {
-		return true
-	}
-	if !env.IsProd() {
-		return false
-	}
-	if strings.HasSuffix(r.Host, ".cn") {
-		return true
-	}
-	switch r.Header.Get("X-AppEngine-Country") {
-	case "", "ZZ", "CN":
-		return true
-	}
-	return false
-}
diff --git a/cmd/golangorg/godoc/redirect/hash.go b/cmd/golangorg/godoc/redirect/hash.go
deleted file mode 100644
index d5a1e3e..0000000
--- a/cmd/golangorg/godoc/redirect/hash.go
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2014 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 provides a compact encoding of
-// a map of Mercurial hashes to Git hashes.
-
-package redirect
-
-import (
-	"encoding/binary"
-	"fmt"
-	"io"
-	"os"
-	"sort"
-	"strconv"
-	"strings"
-)
-
-// hashMap is a map of Mercurial hashes to Git hashes.
-type hashMap struct {
-	file    *os.File
-	entries int
-}
-
-// newHashMap takes a file handle that contains a map of Mercurial to Git
-// hashes. The file should be a sequence of pairs of little-endian encoded
-// uint32s, representing a hgHash and a gitHash respectively.
-// The sequence must be sorted by hgHash.
-// The file must remain open for as long as the returned hashMap is used.
-func newHashMap(f *os.File) (*hashMap, error) {
-	fi, err := f.Stat()
-	if err != nil {
-		return nil, err
-	}
-	return &hashMap{file: f, entries: int(fi.Size() / 8)}, nil
-}
-
-// Lookup finds an hgHash in the map that matches the given prefix, and returns
-// its corresponding gitHash. The prefix must be at least 8 characters long.
-func (m *hashMap) Lookup(s string) gitHash {
-	if m == nil {
-		return 0
-	}
-	hg, err := hgHashFromString(s)
-	if err != nil {
-		return 0
-	}
-	var git gitHash
-	b := make([]byte, 8)
-	sort.Search(m.entries, func(i int) bool {
-		n, err := m.file.ReadAt(b, int64(i*8))
-		if err != nil {
-			panic(err)
-		}
-		if n != 8 {
-			panic(io.ErrUnexpectedEOF)
-		}
-		v := hgHash(binary.LittleEndian.Uint32(b[:4]))
-		if v == hg {
-			git = gitHash(binary.LittleEndian.Uint32(b[4:]))
-		}
-		return v >= hg
-	})
-	return git
-}
-
-// hgHash represents the lower (leftmost) 32 bits of a Mercurial hash.
-type hgHash uint32
-
-func (h hgHash) String() string {
-	return intToHash(int64(h))
-}
-
-func hgHashFromString(s string) (hgHash, error) {
-	if len(s) < 8 {
-		return 0, fmt.Errorf("string too small: len(s) = %d", len(s))
-	}
-	hash := s[:8]
-	i, err := strconv.ParseInt(hash, 16, 64)
-	if err != nil {
-		return 0, err
-	}
-	return hgHash(i), nil
-}
-
-// gitHash represents the leftmost 28 bits of a Git hash in its upper 28 bits,
-// and it encodes hash's repository in the lower 4  bits.
-type gitHash uint32
-
-func (h gitHash) Hash() string {
-	return intToHash(int64(h))[:7]
-}
-
-func (h gitHash) Repo() string {
-	return repo(h & 0xF).String()
-}
-
-func intToHash(i int64) string {
-	s := strconv.FormatInt(i, 16)
-	if len(s) < 8 {
-		s = strings.Repeat("0", 8-len(s)) + s
-	}
-	return s
-}
-
-// repo represents a Go Git repository.
-type repo byte
-
-const (
-	repoGo repo = iota
-	repoBlog
-	repoCrypto
-	repoExp
-	repoImage
-	repoMobile
-	repoNet
-	repoSys
-	repoTalks
-	repoText
-	repoTools
-)
-
-func (r repo) String() string {
-	return map[repo]string{
-		repoGo:     "go",
-		repoBlog:   "blog",
-		repoCrypto: "crypto",
-		repoExp:    "exp",
-		repoImage:  "image",
-		repoMobile: "mobile",
-		repoNet:    "net",
-		repoSys:    "sys",
-		repoTalks:  "talks",
-		repoText:   "text",
-		repoTools:  "tools",
-	}[r]
-}
diff --git a/cmd/golangorg/godoc/redirect/redirect.go b/cmd/golangorg/godoc/redirect/redirect.go
deleted file mode 100644
index 1edc17c..0000000
--- a/cmd/golangorg/godoc/redirect/redirect.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// 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.
-
-// Package redirect provides hooks to register HTTP handlers that redirect old
-// godoc paths to their new equivalents and assist in accessing the issue
-// tracker, wiki, code review system, etc.
-package redirect // import "golang.org/x/website/cmd/golangorg/godoc/redirect"
-
-import (
-	"fmt"
-	"net/http"
-	"os"
-	"regexp"
-	"strconv"
-	"strings"
-)
-
-// Register registers HTTP handlers that redirect old godoc paths to their new
-// equivalents and assist in accessing the issue tracker, wiki, code review
-// system, etc. If mux is nil it uses http.DefaultServeMux.
-func Register(mux *http.ServeMux) {
-	if mux == nil {
-		mux = http.DefaultServeMux
-	}
-	handlePathRedirects(mux, pkgRedirects, "/pkg/")
-	handlePathRedirects(mux, cmdRedirects, "/cmd/")
-	for prefix, redirect := range prefixHelpers {
-		p := "/" + prefix + "/"
-		mux.Handle(p, PrefixHandler(p, redirect))
-	}
-	for path, redirect := range redirects {
-		mux.Handle(path, Handler(redirect))
-	}
-	// NB: /src/pkg (sans trailing slash) is the index of packages.
-	mux.HandleFunc("/src/pkg/", srcPkgHandler)
-	mux.HandleFunc("/cl/", clHandler)
-	mux.HandleFunc("/change/", changeHandler)
-	mux.HandleFunc("/design/", designHandler)
-}
-
-func handlePathRedirects(mux *http.ServeMux, redirects map[string]string, prefix string) {
-	for source, target := range redirects {
-		h := Handler(prefix + target + "/")
-		p := prefix + source
-		mux.Handle(p, h)
-		mux.Handle(p+"/", h)
-	}
-}
-
-// Packages that were renamed between r60 and go1.
-var pkgRedirects = map[string]string{
-	"asn1":              "encoding/asn1",
-	"big":               "math/big",
-	"cmath":             "math/cmplx",
-	"csv":               "encoding/csv",
-	"exec":              "os/exec",
-	"exp/template/html": "html/template",
-	"gob":               "encoding/gob",
-	"http":              "net/http",
-	"http/cgi":          "net/http/cgi",
-	"http/fcgi":         "net/http/fcgi",
-	"http/httptest":     "net/http/httptest",
-	"http/pprof":        "net/http/pprof",
-	"json":              "encoding/json",
-	"mail":              "net/mail",
-	"rand":              "math/rand",
-	"rpc":               "net/rpc",
-	"rpc/jsonrpc":       "net/rpc/jsonrpc",
-	"scanner":           "text/scanner",
-	"smtp":              "net/smtp",
-	"tabwriter":         "text/tabwriter",
-	"template":          "text/template",
-	"template/parse":    "text/template/parse",
-	"url":               "net/url",
-	"utf16":             "unicode/utf16",
-	"utf8":              "unicode/utf8",
-	"xml":               "encoding/xml",
-}
-
-// Commands that were renamed between r60 and go1.
-var cmdRedirects = map[string]string{
-	"gofix":     "fix",
-	"goinstall": "go",
-	"gopack":    "pack",
-	"gotest":    "go",
-	"govet":     "vet",
-	"goyacc":    "yacc",
-}
-
-var redirects = map[string]string{
-	"/blog":       "/blog/",
-	"/build":      "http://build.golang.org",
-	"/change":     "https://go.googlesource.com/go",
-	"/cl":         "https://go-review.googlesource.com",
-	"/cmd/godoc/": "http://godoc.org/golang.org/x/website/cmd/golangorg/",
-	"/issue":      "https://github.com/golang/go/issues",
-	"/issue/new":  "https://github.com/golang/go/issues/new",
-	"/issues":     "https://github.com/golang/go/issues",
-	"/issues/new": "https://github.com/golang/go/issues/new",
-	"/play":       "http://play.golang.org",
-	"/design":     "https://go.googlesource.com/proposal/+/master/design",
-
-	// In Go 1.2 the references page is part of /doc/.
-	"/ref": "/doc/#references",
-	// This next rule clobbers /ref/spec and /ref/mem.
-	// TODO(adg): figure out what to do here, if anything.
-	// "/ref/": "/doc/#references",
-
-	// Be nice to people who are looking in the wrong place.
-	"/doc/mem":  "/ref/mem",
-	"/doc/spec": "/ref/spec",
-
-	"/talks": "http://talks.golang.org",
-	"/tour":  "http://tour.golang.org",
-	"/wiki":  "https://github.com/golang/go/wiki",
-
-	"/doc/articles/c_go_cgo.html":                    "/blog/c-go-cgo",
-	"/doc/articles/concurrency_patterns.html":        "/blog/go-concurrency-patterns-timing-out-and",
-	"/doc/articles/defer_panic_recover.html":         "/blog/defer-panic-and-recover",
-	"/doc/articles/error_handling.html":              "/blog/error-handling-and-go",
-	"/doc/articles/gobs_of_data.html":                "/blog/gobs-of-data",
-	"/doc/articles/godoc_documenting_go_code.html":   "/blog/godoc-documenting-go-code",
-	"/doc/articles/gos_declaration_syntax.html":      "/blog/gos-declaration-syntax",
-	"/doc/articles/image_draw.html":                  "/blog/go-imagedraw-package",
-	"/doc/articles/image_package.html":               "/blog/go-image-package",
-	"/doc/articles/json_and_go.html":                 "/blog/json-and-go",
-	"/doc/articles/json_rpc_tale_of_interfaces.html": "/blog/json-rpc-tale-of-interfaces",
-	"/doc/articles/laws_of_reflection.html":          "/blog/laws-of-reflection",
-	"/doc/articles/slices_usage_and_internals.html":  "/blog/go-slices-usage-and-internals",
-	"/doc/go_for_cpp_programmers.html":               "/wiki/GoForCPPProgrammers",
-	"/doc/go_tutorial.html":                          "http://tour.golang.org/",
-}
-
-var prefixHelpers = map[string]string{
-	"issue":  "https://github.com/golang/go/issues/",
-	"issues": "https://github.com/golang/go/issues/",
-	"play":   "http://play.golang.org/",
-	"talks":  "http://talks.golang.org/",
-	"wiki":   "https://github.com/golang/go/wiki/",
-}
-
-func Handler(target string) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		url := target
-		if qs := r.URL.RawQuery; qs != "" {
-			url += "?" + qs
-		}
-		http.Redirect(w, r, url, http.StatusMovedPermanently)
-	})
-}
-
-var validId = regexp.MustCompile(`^[A-Za-z0-9-]*/?$`)
-
-func PrefixHandler(prefix, baseURL string) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if p := r.URL.Path; p == prefix {
-			// redirect /prefix/ to /prefix
-			http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
-			return
-		}
-		id := r.URL.Path[len(prefix):]
-		if !validId.MatchString(id) {
-			http.Error(w, "Not found", http.StatusNotFound)
-			return
-		}
-		target := baseURL + id
-		http.Redirect(w, r, target, http.StatusFound)
-	})
-}
-
-// Redirect requests from the old "/src/pkg/foo" to the new "/src/foo".
-// See http://golang.org/s/go14nopkg
-func srcPkgHandler(w http.ResponseWriter, r *http.Request) {
-	r.URL.Path = "/src/" + r.URL.Path[len("/src/pkg/"):]
-	http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
-}
-
-func clHandler(w http.ResponseWriter, r *http.Request) {
-	const prefix = "/cl/"
-	if p := r.URL.Path; p == prefix {
-		// redirect /prefix/ to /prefix
-		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
-		return
-	}
-	id := r.URL.Path[len(prefix):]
-	// support /cl/152700045/, which is used in commit 0edafefc36.
-	id = strings.TrimSuffix(id, "/")
-	if !validId.MatchString(id) {
-		http.Error(w, "Not found", http.StatusNotFound)
-		return
-	}
-	target := ""
-
-	if n, err := strconv.Atoi(id); err == nil && isRietveldCL(n) {
-		// TODO: Issue 28836: if this Rietveld CL happens to
-		// also be a Gerrit CL, render a disambiguation HTML
-		// page with two links instead. We'll need to make an
-		// RPC (to maintner?) to figure that out. For now just
-		// redirect to rietveld.
-		target = "https://codereview.appspot.com/" + id
-	} else {
-		target = "https://go-review.googlesource.com/" + id
-	}
-	http.Redirect(w, r, target, http.StatusFound)
-}
-
-var changeMap *hashMap
-
-// LoadChangeMap loads the specified map of Mercurial to Git revisions,
-// which is used by the /change/ handler to intelligently map old hg
-// revisions to their new git equivalents.
-// It should be called before calling Register.
-// The file should remain open as long as the process is running.
-// See the implementation of this package for details.
-func LoadChangeMap(filename string) error {
-	f, err := os.Open(filename)
-	if err != nil {
-		return err
-	}
-	m, err := newHashMap(f)
-	if err != nil {
-		return err
-	}
-	changeMap = m
-	return nil
-}
-
-func changeHandler(w http.ResponseWriter, r *http.Request) {
-	const prefix = "/change/"
-	if p := r.URL.Path; p == prefix {
-		// redirect /prefix/ to /prefix
-		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
-		return
-	}
-	hash := r.URL.Path[len(prefix):]
-	target := "https://go.googlesource.com/go/+/" + hash
-	if git := changeMap.Lookup(hash); git > 0 {
-		target = fmt.Sprintf("https://go.googlesource.com/%v/+/%v", git.Repo(), git.Hash())
-	}
-	http.Redirect(w, r, target, http.StatusFound)
-}
-
-func designHandler(w http.ResponseWriter, r *http.Request) {
-	const prefix = "/design/"
-	if p := r.URL.Path; p == prefix {
-		// redirect /prefix/ to /prefix
-		http.Redirect(w, r, p[:len(p)-1], http.StatusFound)
-		return
-	}
-	name := r.URL.Path[len(prefix):]
-	target := "https://go.googlesource.com/proposal/+/master/design/" + name + ".md"
-	http.Redirect(w, r, target, http.StatusFound)
-}
diff --git a/cmd/golangorg/godoc/redirect/redirect_test.go b/cmd/golangorg/godoc/redirect/redirect_test.go
deleted file mode 100644
index 804bfb0..0000000
--- a/cmd/golangorg/godoc/redirect/redirect_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2015 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.
-
-package redirect
-
-import (
-	"net/http"
-	"net/http/httptest"
-	"testing"
-)
-
-type redirectResult struct {
-	status int
-	path   string
-}
-
-func errorResult(status int) redirectResult {
-	return redirectResult{status, ""}
-}
-
-func TestRedirects(t *testing.T) {
-	var tests = map[string]redirectResult{
-		"/build":    {301, "http://build.golang.org"},
-		"/ref":      {301, "/doc/#references"},
-		"/doc/mem":  {301, "/ref/mem"},
-		"/doc/spec": {301, "/ref/spec"},
-		"/tour":     {301, "http://tour.golang.org"},
-		"/foo":      errorResult(404),
-
-		"/pkg/asn1":           {301, "/pkg/encoding/asn1/"},
-		"/pkg/template/parse": {301, "/pkg/text/template/parse/"},
-
-		"/src/pkg/foo": {301, "/src/foo"},
-
-		"/cmd/gofix": {301, "/cmd/fix/"},
-
-		// git commits (/change)
-		// TODO: mercurial tags and LoadChangeMap.
-		"/change":   {301, "https://go.googlesource.com/go"},
-		"/change/a": {302, "https://go.googlesource.com/go/+/a"},
-
-		"/issue":                    {301, "https://github.com/golang/go/issues"},
-		"/issue?":                   {301, "https://github.com/golang/go/issues"},
-		"/issue/1":                  {302, "https://github.com/golang/go/issues/1"},
-		"/issue/new":                {301, "https://github.com/golang/go/issues/new"},
-		"/issue/new?a=b&c=d%20&e=f": {301, "https://github.com/golang/go/issues/new?a=b&c=d%20&e=f"},
-		"/issues":                   {301, "https://github.com/golang/go/issues"},
-		"/issues/1":                 {302, "https://github.com/golang/go/issues/1"},
-		"/issues/new":               {301, "https://github.com/golang/go/issues/new"},
-		"/issues/1/2/3":             errorResult(404),
-
-		"/wiki/foo":  {302, "https://github.com/golang/go/wiki/foo"},
-		"/wiki/foo/": {302, "https://github.com/golang/go/wiki/foo/"},
-
-		"/design":              {301, "https://go.googlesource.com/proposal/+/master/design"},
-		"/design/":             {302, "/design"},
-		"/design/123-foo":      {302, "https://go.googlesource.com/proposal/+/master/design/123-foo.md"},
-		"/design/text/123-foo": {302, "https://go.googlesource.com/proposal/+/master/design/text/123-foo.md"},
-
-		"/cl/1":          {302, "https://go-review.googlesource.com/1"},
-		"/cl/1/":         {302, "https://go-review.googlesource.com/1"},
-		"/cl/267120043":  {302, "https://codereview.appspot.com/267120043"},
-		"/cl/267120043/": {302, "https://codereview.appspot.com/267120043"},
-
-		// Verify that we're using the Rietveld CL table:
-		"/cl/152046": {302, "https://codereview.appspot.com/152046"},
-		"/cl/152047": {302, "https://go-review.googlesource.com/152047"},
-		"/cl/152048": {302, "https://codereview.appspot.com/152048"},
-
-		// And verify we're using the the "bigEnoughAssumeRietveld" value:
-		"/cl/299999": {302, "https://go-review.googlesource.com/299999"},
-		"/cl/300000": {302, "https://codereview.appspot.com/300000"},
-	}
-
-	mux := http.NewServeMux()
-	Register(mux)
-	ts := httptest.NewServer(mux)
-	defer ts.Close()
-
-	for path, want := range tests {
-		if want.path != "" && want.path[0] == '/' {
-			// All redirects are absolute.
-			want.path = ts.URL + want.path
-		}
-
-		req, err := http.NewRequest("GET", ts.URL+path, nil)
-		if err != nil {
-			t.Errorf("(path: %q) unexpected error: %v", path, err)
-			continue
-		}
-
-		resp, err := http.DefaultTransport.RoundTrip(req)
-		if err != nil {
-			t.Errorf("(path: %q) unexpected error: %v", path, err)
-			continue
-		}
-
-		if resp.StatusCode != want.status {
-			t.Errorf("(path: %q) got status %d, want %d", path, resp.StatusCode, want.status)
-		}
-
-		if want.status != 301 && want.status != 302 {
-			// Not a redirect. Just check status.
-			continue
-		}
-
-		out, _ := resp.Location()
-		if got := out.String(); got != want.path {
-			t.Errorf("(path: %q) got %s, want %s", path, got, want.path)
-		}
-	}
-}
diff --git a/cmd/golangorg/godoc/redirect/rietveld.go b/cmd/golangorg/godoc/redirect/rietveld.go
deleted file mode 100644
index 81b1094..0000000
--- a/cmd/golangorg/godoc/redirect/rietveld.go
+++ /dev/null
@@ -1,1093 +0,0 @@
-// Copyright 2018 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.
-
-package redirect
-
-// bigEnoughAssumeRietveld is the value where CLs equal or great are
-// assumed to be on Rietveld. By including this threshold we shrink
-// the size of the table below. When Go amasses 150,000 more CLs, we'll
-// need to bump this number and regenerate the list below.
-const bigEnoughAssumeRietveld = 300000
-
-// isRietveldCL reports whether cl was a Rietveld CL number.
-func isRietveldCL(cl int) bool {
-	return cl >= bigEnoughAssumeRietveld || lowRietveldCL[cl]
-}
-
-// lowRietveldCLs are the old CL numbers assigned by Rietveld code
-// review system as used by Go prior to Gerrit which are less than
-// bigEnoughAssumeRietveld.
-//
-// This list of numbers is registered with the /cl/NNNN redirect
-// handler to disambiguate which code review system a particular
-// number corresponds to. In some rare cases there may be duplicates,
-// in which case we might render an HTML choice for the user.
-//
-// To re-generate this list, run:
-//
-// $ cd $GOROOT
-// $ git log 7d7c6a9..94151eb | grep "^    https://golang.org/cl/" | perl -ne 's,^\s+https://golang.org/cl/(\d+).*$,$1,; chomp; print "$_: true,\n" if $_ < 300000' | sort -n | uniq
-//
-// Note that we ignore the x/* repos because we didn't start using
-// "subrepos" until the Rietveld CLs numbers were already 4,000,000+,
-// well above bigEnoughAssumeRietveld.
-var lowRietveldCL = map[int]bool{
-	152046: true,
-	152048: true,
-	152049: true,
-	152050: true,
-	152051: true,
-	152052: true,
-	152055: true,
-	152056: true,
-	152057: true,
-	152072: true,
-	152073: true,
-	152075: true,
-	152076: true,
-	152077: true,
-	152078: true,
-	152079: true,
-	152080: true,
-	152082: true,
-	152084: true,
-	152085: true,
-	152086: true,
-	152088: true,
-	152089: true,
-	152091: true,
-	152098: true,
-	152101: true,
-	152102: true,
-	152105: true,
-	152106: true,
-	152107: true,
-	152108: true,
-	152109: true,
-	152110: true,
-	152114: true,
-	152117: true,
-	152118: true,
-	152120: true,
-	152123: true,
-	152124: true,
-	152128: true,
-	152130: true,
-	152131: true,
-	152138: true,
-	152141: true,
-	152142: true,
-	153048: true,
-	153049: true,
-	153050: true,
-	153051: true,
-	153055: true,
-	153056: true,
-	153057: true,
-	154043: true,
-	154044: true,
-	154045: true,
-	154049: true,
-	154055: true,
-	154057: true,
-	154058: true,
-	154059: true,
-	154061: true,
-	154064: true,
-	154065: true,
-	154067: true,
-	154068: true,
-	154069: true,
-	154071: true,
-	154072: true,
-	154073: true,
-	154076: true,
-	154079: true,
-	154096: true,
-	154097: true,
-	154099: true,
-	154100: true,
-	154101: true,
-	154102: true,
-	154108: true,
-	154118: true,
-	154121: true,
-	154122: true,
-	154123: true,
-	154125: true,
-	154126: true,
-	154128: true,
-	154136: true,
-	154138: true,
-	154139: true,
-	154140: true,
-	154141: true,
-	154142: true,
-	154143: true,
-	154144: true,
-	154145: true,
-	154146: true,
-	154152: true,
-	154153: true,
-	154156: true,
-	154159: true,
-	154161: true,
-	154166: true,
-	154167: true,
-	154169: true,
-	154171: true,
-	154172: true,
-	154173: true,
-	154174: true,
-	154175: true,
-	154176: true,
-	154177: true,
-	154178: true,
-	154179: true,
-	154180: true,
-	155041: true,
-	155042: true,
-	155045: true,
-	155047: true,
-	155048: true,
-	155049: true,
-	155050: true,
-	155054: true,
-	155055: true,
-	155056: true,
-	155057: true,
-	155058: true,
-	155059: true,
-	155061: true,
-	155062: true,
-	155063: true,
-	155065: true,
-	155067: true,
-	155069: true,
-	155072: true,
-	155074: true,
-	155075: true,
-	155077: true,
-	155078: true,
-	155079: true,
-	156041: true,
-	156044: true,
-	156045: true,
-	156046: true,
-	156047: true,
-	156051: true,
-	156052: true,
-	156054: true,
-	156055: true,
-	156056: true,
-	156058: true,
-	156059: true,
-	156060: true,
-	156061: true,
-	156062: true,
-	156063: true,
-	156066: true,
-	156067: true,
-	156070: true,
-	156071: true,
-	156073: true,
-	156075: true,
-	156077: true,
-	156079: true,
-	156080: true,
-	156081: true,
-	156083: true,
-	156084: true,
-	156085: true,
-	156086: true,
-	156089: true,
-	156091: true,
-	156092: true,
-	156093: true,
-	156094: true,
-	156097: true,
-	156099: true,
-	156100: true,
-	156102: true,
-	156103: true,
-	156104: true,
-	156106: true,
-	156107: true,
-	156108: true,
-	156109: true,
-	156110: true,
-	156113: true,
-	156115: true,
-	156116: true,
-	157041: true,
-	157042: true,
-	157043: true,
-	157044: true,
-	157046: true,
-	157053: true,
-	157055: true,
-	157056: true,
-	157058: true,
-	157060: true,
-	157061: true,
-	157062: true,
-	157065: true,
-	157066: true,
-	157067: true,
-	157068: true,
-	157069: true,
-	157071: true,
-	157072: true,
-	157073: true,
-	157074: true,
-	157075: true,
-	157076: true,
-	157077: true,
-	157082: true,
-	157084: true,
-	157085: true,
-	157087: true,
-	157088: true,
-	157091: true,
-	157095: true,
-	157096: true,
-	157099: true,
-	157100: true,
-	157101: true,
-	157102: true,
-	157103: true,
-	157104: true,
-	157106: true,
-	157110: true,
-	157111: true,
-	157112: true,
-	157114: true,
-	157116: true,
-	157119: true,
-	157140: true,
-	157142: true,
-	157143: true,
-	157144: true,
-	157146: true,
-	157147: true,
-	157149: true,
-	157151: true,
-	157152: true,
-	157153: true,
-	157154: true,
-	157156: true,
-	157157: true,
-	157158: true,
-	157159: true,
-	157160: true,
-	157162: true,
-	157166: true,
-	157167: true,
-	157168: true,
-	157170: true,
-	158041: true,
-	159044: true,
-	159049: true,
-	159050: true,
-	159051: true,
-	160043: true,
-	160044: true,
-	160045: true,
-	160046: true,
-	160047: true,
-	160054: true,
-	160056: true,
-	160057: true,
-	160059: true,
-	160060: true,
-	160061: true,
-	160064: true,
-	160065: true,
-	160069: true,
-	160070: true,
-	161049: true,
-	161050: true,
-	161056: true,
-	161058: true,
-	161060: true,
-	161061: true,
-	161069: true,
-	161070: true,
-	161073: true,
-	161075: true,
-	162041: true,
-	162044: true,
-	162046: true,
-	162053: true,
-	162054: true,
-	162055: true,
-	162056: true,
-	162057: true,
-	162058: true,
-	162059: true,
-	162061: true,
-	162062: true,
-	163042: true,
-	163044: true,
-	163049: true,
-	163050: true,
-	163051: true,
-	163052: true,
-	163053: true,
-	163055: true,
-	163058: true,
-	163061: true,
-	163062: true,
-	163064: true,
-	163067: true,
-	163068: true,
-	163069: true,
-	163070: true,
-	163071: true,
-	163072: true,
-	163082: true,
-	163083: true,
-	163085: true,
-	163088: true,
-	163091: true,
-	163092: true,
-	163097: true,
-	163098: true,
-	164043: true,
-	164047: true,
-	164049: true,
-	164052: true,
-	164053: true,
-	164056: true,
-	164059: true,
-	164060: true,
-	164062: true,
-	164068: true,
-	164069: true,
-	164071: true,
-	164073: true,
-	164074: true,
-	164075: true,
-	164078: true,
-	164079: true,
-	164081: true,
-	164082: true,
-	164083: true,
-	164085: true,
-	164086: true,
-	164088: true,
-	164090: true,
-	164091: true,
-	164092: true,
-	164093: true,
-	164094: true,
-	164095: true,
-	165042: true,
-	165044: true,
-	165045: true,
-	165048: true,
-	165049: true,
-	165050: true,
-	165051: true,
-	165055: true,
-	165057: true,
-	165058: true,
-	165059: true,
-	165061: true,
-	165062: true,
-	165063: true,
-	165064: true,
-	165065: true,
-	165068: true,
-	165070: true,
-	165076: true,
-	165078: true,
-	165080: true,
-	165083: true,
-	165086: true,
-	165097: true,
-	165100: true,
-	165101: true,
-	166041: true,
-	166043: true,
-	166044: true,
-	166047: true,
-	166049: true,
-	166052: true,
-	166053: true,
-	166055: true,
-	166058: true,
-	166059: true,
-	166060: true,
-	166064: true,
-	166066: true,
-	166067: true,
-	166068: true,
-	166070: true,
-	166071: true,
-	166072: true,
-	166073: true,
-	166074: true,
-	166076: true,
-	166077: true,
-	166078: true,
-	166080: true,
-	167043: true,
-	167044: true,
-	167047: true,
-	167050: true,
-	167055: true,
-	167057: true,
-	167058: true,
-	168041: true,
-	168045: true,
-	170042: true,
-	170043: true,
-	170044: true,
-	170046: true,
-	170047: true,
-	170048: true,
-	170049: true,
-	171044: true,
-	171046: true,
-	171047: true,
-	171048: true,
-	171051: true,
-	172041: true,
-	172042: true,
-	172043: true,
-	172045: true,
-	172049: true,
-	173041: true,
-	173044: true,
-	173045: true,
-	174042: true,
-	174047: true,
-	174048: true,
-	174050: true,
-	174051: true,
-	174052: true,
-	174053: true,
-	174063: true,
-	174064: true,
-	174072: true,
-	174076: true,
-	174077: true,
-	174078: true,
-	174082: true,
-	174083: true,
-	174087: true,
-	175045: true,
-	175046: true,
-	175047: true,
-	175048: true,
-	176056: true,
-	176057: true,
-	176058: true,
-	176061: true,
-	176062: true,
-	176063: true,
-	176064: true,
-	176066: true,
-	176067: true,
-	176070: true,
-	176071: true,
-	176076: true,
-	178043: true,
-	178044: true,
-	178046: true,
-	178048: true,
-	179047: true,
-	179055: true,
-	179061: true,
-	179062: true,
-	179063: true,
-	179067: true,
-	179069: true,
-	179070: true,
-	179072: true,
-	179079: true,
-	179088: true,
-	179095: true,
-	179096: true,
-	179097: true,
-	179099: true,
-	179105: true,
-	179106: true,
-	179108: true,
-	179118: true,
-	179120: true,
-	179125: true,
-	179126: true,
-	179128: true,
-	179129: true,
-	179130: true,
-	180044: true,
-	180045: true,
-	180046: true,
-	180047: true,
-	180048: true,
-	180049: true,
-	180050: true,
-	180052: true,
-	180053: true,
-	180054: true,
-	180055: true,
-	180056: true,
-	180057: true,
-	180059: true,
-	180061: true,
-	180064: true,
-	180065: true,
-	180068: true,
-	180069: true,
-	180070: true,
-	180074: true,
-	180075: true,
-	180081: true,
-	180082: true,
-	180085: true,
-	180092: true,
-	180099: true,
-	180105: true,
-	180108: true,
-	180112: true,
-	180118: true,
-	181041: true,
-	181043: true,
-	181044: true,
-	181045: true,
-	181049: true,
-	181050: true,
-	181055: true,
-	181057: true,
-	181058: true,
-	181059: true,
-	181063: true,
-	181071: true,
-	181073: true,
-	181075: true,
-	181077: true,
-	181080: true,
-	181083: true,
-	181084: true,
-	181085: true,
-	181086: true,
-	181087: true,
-	181089: true,
-	181097: true,
-	181099: true,
-	181102: true,
-	181111: true,
-	181130: true,
-	181135: true,
-	181137: true,
-	181138: true,
-	181139: true,
-	181151: true,
-	181152: true,
-	181153: true,
-	181155: true,
-	181156: true,
-	181157: true,
-	181158: true,
-	181160: true,
-	181161: true,
-	181163: true,
-	181164: true,
-	181171: true,
-	181179: true,
-	181183: true,
-	181184: true,
-	181186: true,
-	182041: true,
-	182043: true,
-	182044: true,
-	183042: true,
-	183043: true,
-	183044: true,
-	183047: true,
-	183049: true,
-	183065: true,
-	183066: true,
-	183073: true,
-	183074: true,
-	183075: true,
-	183083: true,
-	183084: true,
-	183087: true,
-	183088: true,
-	183090: true,
-	183095: true,
-	183104: true,
-	183107: true,
-	183109: true,
-	183111: true,
-	183112: true,
-	183113: true,
-	183116: true,
-	183123: true,
-	183124: true,
-	183125: true,
-	183126: true,
-	183132: true,
-	183133: true,
-	183135: true,
-	183136: true,
-	183137: true,
-	183138: true,
-	183139: true,
-	183140: true,
-	183141: true,
-	183142: true,
-	183153: true,
-	183155: true,
-	183156: true,
-	183157: true,
-	183160: true,
-	184043: true,
-	184055: true,
-	184058: true,
-	184059: true,
-	184068: true,
-	184069: true,
-	184079: true,
-	184080: true,
-	184081: true,
-	185043: true,
-	185045: true,
-	186042: true,
-	186043: true,
-	186073: true,
-	186076: true,
-	186077: true,
-	186078: true,
-	186079: true,
-	186081: true,
-	186095: true,
-	186108: true,
-	186113: true,
-	186115: true,
-	186116: true,
-	186118: true,
-	186119: true,
-	186132: true,
-	186137: true,
-	186138: true,
-	186139: true,
-	186143: true,
-	186144: true,
-	186145: true,
-	186146: true,
-	186147: true,
-	186148: true,
-	186159: true,
-	186160: true,
-	186161: true,
-	186165: true,
-	186169: true,
-	186173: true,
-	186180: true,
-	186210: true,
-	186211: true,
-	186212: true,
-	186213: true,
-	186214: true,
-	186215: true,
-	186216: true,
-	186228: true,
-	186229: true,
-	186230: true,
-	186232: true,
-	186234: true,
-	186255: true,
-	186263: true,
-	186276: true,
-	186279: true,
-	186282: true,
-	186283: true,
-	188043: true,
-	189042: true,
-	189057: true,
-	189059: true,
-	189062: true,
-	189078: true,
-	189080: true,
-	189083: true,
-	189088: true,
-	189093: true,
-	189095: true,
-	189096: true,
-	189098: true,
-	189100: true,
-	190041: true,
-	190042: true,
-	190043: true,
-	190044: true,
-	190059: true,
-	190062: true,
-	190068: true,
-	190074: true,
-	190076: true,
-	190077: true,
-	190079: true,
-	190085: true,
-	190088: true,
-	190103: true,
-	190104: true,
-	193055: true,
-	193066: true,
-	193067: true,
-	193070: true,
-	193075: true,
-	193079: true,
-	193080: true,
-	193081: true,
-	193091: true,
-	193092: true,
-	193095: true,
-	193101: true,
-	193104: true,
-	194043: true,
-	194045: true,
-	194046: true,
-	194050: true,
-	194051: true,
-	194052: true,
-	194053: true,
-	194064: true,
-	194066: true,
-	194069: true,
-	194071: true,
-	194072: true,
-	194073: true,
-	194074: true,
-	194076: true,
-	194077: true,
-	194078: true,
-	194082: true,
-	194084: true,
-	194085: true,
-	194090: true,
-	194091: true,
-	194092: true,
-	194094: true,
-	194097: true,
-	194098: true,
-	194099: true,
-	194100: true,
-	194114: true,
-	194116: true,
-	194118: true,
-	194119: true,
-	194120: true,
-	194121: true,
-	194122: true,
-	194126: true,
-	194129: true,
-	194131: true,
-	194132: true,
-	194133: true,
-	194134: true,
-	194146: true,
-	194151: true,
-	194156: true,
-	194157: true,
-	194159: true,
-	194161: true,
-	194165: true,
-	195041: true,
-	195044: true,
-	195050: true,
-	195051: true,
-	195052: true,
-	195068: true,
-	195075: true,
-	195076: true,
-	195079: true,
-	195080: true,
-	195081: true,
-	196042: true,
-	196044: true,
-	196050: true,
-	196051: true,
-	196055: true,
-	196056: true,
-	196061: true,
-	196063: true,
-	196065: true,
-	196070: true,
-	196071: true,
-	196075: true,
-	196077: true,
-	196079: true,
-	196087: true,
-	196088: true,
-	196090: true,
-	196091: true,
-	197041: true,
-	197042: true,
-	197043: true,
-	197044: true,
-	198044: true,
-	198045: true,
-	198046: true,
-	198048: true,
-	198049: true,
-	198050: true,
-	198053: true,
-	198057: true,
-	198058: true,
-	198066: true,
-	198071: true,
-	198074: true,
-	198081: true,
-	198084: true,
-	198085: true,
-	198102: true,
-	199042: true,
-	199044: true,
-	199045: true,
-	199046: true,
-	199047: true,
-	199052: true,
-	199054: true,
-	199057: true,
-	199066: true,
-	199070: true,
-	199082: true,
-	199091: true,
-	199094: true,
-	199096: true,
-	201041: true,
-	201042: true,
-	201043: true,
-	201047: true,
-	201048: true,
-	201049: true,
-	201058: true,
-	201061: true,
-	201064: true,
-	201065: true,
-	201068: true,
-	202042: true,
-	202043: true,
-	202044: true,
-	202051: true,
-	202054: true,
-	202055: true,
-	203043: true,
-	203050: true,
-	203051: true,
-	203053: true,
-	203060: true,
-	203062: true,
-	204042: true,
-	204044: true,
-	204048: true,
-	204052: true,
-	204053: true,
-	204061: true,
-	204062: true,
-	204064: true,
-	204065: true,
-	204067: true,
-	204068: true,
-	204069: true,
-	205042: true,
-	205044: true,
-	206043: true,
-	206044: true,
-	206047: true,
-	206050: true,
-	206051: true,
-	206052: true,
-	206053: true,
-	206054: true,
-	206055: true,
-	206058: true,
-	206059: true,
-	206060: true,
-	206067: true,
-	206069: true,
-	206077: true,
-	206078: true,
-	206079: true,
-	206084: true,
-	206089: true,
-	206101: true,
-	206107: true,
-	206109: true,
-	207043: true,
-	207044: true,
-	207049: true,
-	207050: true,
-	207051: true,
-	207052: true,
-	207053: true,
-	207054: true,
-	207055: true,
-	207061: true,
-	207062: true,
-	207069: true,
-	207071: true,
-	207085: true,
-	207086: true,
-	207087: true,
-	207088: true,
-	207095: true,
-	207096: true,
-	207102: true,
-	207103: true,
-	207106: true,
-	207108: true,
-	207110: true,
-	207111: true,
-	207112: true,
-	209041: true,
-	209042: true,
-	209043: true,
-	209044: true,
-	210042: true,
-	210043: true,
-	210044: true,
-	210047: true,
-	211041: true,
-	212041: true,
-	212045: true,
-	212046: true,
-	212047: true,
-	213041: true,
-	213042: true,
-	214042: true,
-	214046: true,
-	214049: true,
-	214050: true,
-	215042: true,
-	215048: true,
-	215050: true,
-	216043: true,
-	216046: true,
-	216047: true,
-	216052: true,
-	216053: true,
-	216054: true,
-	216059: true,
-	216068: true,
-	217041: true,
-	217044: true,
-	217047: true,
-	217048: true,
-	217049: true,
-	217056: true,
-	217058: true,
-	217059: true,
-	217060: true,
-	217061: true,
-	217064: true,
-	217066: true,
-	217069: true,
-	217071: true,
-	217085: true,
-	217086: true,
-	217088: true,
-	217093: true,
-	217094: true,
-	217108: true,
-	217109: true,
-	217111: true,
-	217115: true,
-	217116: true,
-	218042: true,
-	218044: true,
-	218046: true,
-	218050: true,
-	218060: true,
-	218061: true,
-	218063: true,
-	218064: true,
-	218065: true,
-	218070: true,
-	218071: true,
-	218072: true,
-	218074: true,
-	218076: true,
-	222041: true,
-	223041: true,
-	223043: true,
-	223044: true,
-	223050: true,
-	223052: true,
-	223054: true,
-	223058: true,
-	223059: true,
-	223061: true,
-	223068: true,
-	223069: true,
-	223070: true,
-	223071: true,
-	223073: true,
-	223075: true,
-	223076: true,
-	223083: true,
-	223087: true,
-	223094: true,
-	223096: true,
-	223101: true,
-	223106: true,
-	223108: true,
-	224041: true,
-	224042: true,
-	224043: true,
-	224045: true,
-	224051: true,
-	224053: true,
-	224057: true,
-	224060: true,
-	224061: true,
-	224062: true,
-	224063: true,
-	224068: true,
-	224069: true,
-	224081: true,
-	224084: true,
-	224087: true,
-	224090: true,
-	224096: true,
-	224105: true,
-	225042: true,
-	227041: true,
-	229045: true,
-	229046: true,
-	229048: true,
-	229049: true,
-	229050: true,
-	231042: true,
-	236041: true,
-	237041: true,
-	238041: true,
-	238042: true,
-	240041: true,
-	240042: true,
-	240043: true,
-	241041: true,
-	243041: true,
-	244041: true,
-	245041: true,
-	247041: true,
-	250041: true,
-	252041: true,
-	253041: true,
-	253045: true,
-	254043: true,
-	255042: true,
-	255043: true,
-	257041: true,
-	257042: true,
-	258041: true,
-	261041: true,
-	264041: true,
-	294042: true,
-	296042: true,
-}
diff --git a/cmd/golangorg/godoc/search.go b/cmd/golangorg/godoc/search.go
deleted file mode 100644
index 0e7509a..0000000
--- a/cmd/golangorg/godoc/search.go
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2009 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.
-
-package godoc
-
-import (
-	"bytes"
-	"fmt"
-	"net/http"
-	"regexp"
-	"strings"
-)
-
-type SearchResult struct {
-	Query string
-	Alert string // error or warning message
-
-	// identifier matches
-	Pak HitList       // packages matching Query
-	Hit *LookupResult // identifier matches of Query
-	Alt *AltWords     // alternative identifiers to look for
-
-	// textual matches
-	Found    int         // number of textual occurrences found
-	Textual  []FileLines // textual matches of Query
-	Complete bool        // true if all textual occurrences of Query are reported
-	Idents   map[SpotKind][]Ident
-}
-
-func (c *Corpus) Lookup(query string) SearchResult {
-	result := &SearchResult{Query: query}
-
-	index, timestamp := c.CurrentIndex()
-	if index != nil {
-		// identifier search
-		if r, err := index.Lookup(query); err == nil {
-			result = r
-		} else if err != nil && !c.IndexFullText {
-			// ignore the error if full text search is enabled
-			// since the query may be a valid regular expression
-			result.Alert = "Error in query string: " + err.Error()
-			return *result
-		}
-
-		// full text search
-		if c.IndexFullText && query != "" {
-			rx, err := regexp.Compile(query)
-			if err != nil {
-				result.Alert = "Error in query regular expression: " + err.Error()
-				return *result
-			}
-			// If we get maxResults+1 results we know that there are more than
-			// maxResults results and thus the result may be incomplete (to be
-			// precise, we should remove one result from the result set, but
-			// nobody is going to count the results on the result page).
-			result.Found, result.Textual = index.LookupRegexp(rx, c.MaxResults+1)
-			result.Complete = result.Found <= c.MaxResults
-			if !result.Complete {
-				result.Found-- // since we looked for maxResults+1
-			}
-		}
-	}
-
-	// is the result accurate?
-	if c.IndexEnabled {
-		if ts := c.FSModifiedTime(); timestamp.Before(ts) {
-			// The index is older than the latest file system change under godoc's observation.
-			result.Alert = "Indexing in progress: result may be inaccurate"
-		}
-	} else {
-		result.Alert = "Search index disabled: no results available"
-	}
-
-	return *result
-}
-
-// SearchResultDoc optionally specifies a function returning an HTML body
-// displaying search results matching godoc documentation.
-func (p *Presentation) SearchResultDoc(result SearchResult) []byte {
-	return applyTemplate(p.SearchDocHTML, "searchDocHTML", result)
-}
-
-// SearchResultCode optionally specifies a function returning an HTML body
-// displaying search results matching source code.
-func (p *Presentation) SearchResultCode(result SearchResult) []byte {
-	return applyTemplate(p.SearchCodeHTML, "searchCodeHTML", result)
-}
-
-// SearchResultTxt optionally specifies a function returning an HTML body
-// displaying search results of textual matches.
-func (p *Presentation) SearchResultTxt(result SearchResult) []byte {
-	return applyTemplate(p.SearchTxtHTML, "searchTxtHTML", result)
-}
-
-// HandleSearch obtains results for the requested search and returns a page
-// to display them.
-func (p *Presentation) HandleSearch(w http.ResponseWriter, r *http.Request) {
-	query := strings.TrimSpace(r.FormValue("q"))
-	result := p.Corpus.Lookup(query)
-
-	var contents bytes.Buffer
-	for _, f := range p.SearchResults {
-		contents.Write(f(p, result))
-	}
-
-	var title string
-	if haveResults := contents.Len() > 0; haveResults {
-		title = fmt.Sprintf(`Results for query: %v`, query)
-		if !p.Corpus.IndexEnabled {
-			result.Alert = ""
-		}
-	} else {
-		title = fmt.Sprintf(`No results found for query %q`, query)
-	}
-
-	body := bytes.NewBuffer(applyTemplate(p.SearchHTML, "searchHTML", result))
-	body.Write(contents.Bytes())
-
-	p.ServePage(w, Page{
-		Title:    title,
-		Tabtitle: query,
-		Query:    query,
-		Body:     body.Bytes(),
-		GoogleCN: googleCN(r),
-	})
-}
-
-func (p *Presentation) serveSearchDesc(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/opensearchdescription+xml")
-	data := map[string]interface{}{
-		"BaseURL": fmt.Sprintf("http://%s", r.Host),
-	}
-	applyTemplateToResponseWriter(w, p.SearchDescXML, &data)
-}
-
-// tocColCount returns the no. of columns
-// to split the toc table to.
-func tocColCount(result SearchResult) int {
-	tocLen := tocLen(result)
-	colCount := 0
-	// Simple heuristic based on visual aesthetic in manual testing.
-	switch {
-	case tocLen <= 10:
-		colCount = 1
-	case tocLen <= 20:
-		colCount = 2
-	case tocLen <= 80:
-		colCount = 3
-	default:
-		colCount = 4
-	}
-	return colCount
-}
-
-// tocLen calculates the no. of items in the toc table
-// by going through various fields in the SearchResult
-// that is rendered in the UI.
-func tocLen(result SearchResult) int {
-	tocLen := 0
-	for _, val := range result.Idents {
-		if len(val) != 0 {
-			tocLen++
-		}
-	}
-	// If no identifiers, then just one item for the header text "Package <result.Query>".
-	// See searchcode.html for further details.
-	if len(result.Idents) == 0 {
-		tocLen++
-	}
-	if result.Hit != nil {
-		if len(result.Hit.Decls) > 0 {
-			tocLen += len(result.Hit.Decls)
-			// We need one extra item for the header text "Package-level declarations".
-			tocLen++
-		}
-		if len(result.Hit.Others) > 0 {
-			tocLen += len(result.Hit.Others)
-			// We need one extra item for the header text "Local declarations and uses".
-			tocLen++
-		}
-	}
-	// For "textual occurrences".
-	tocLen++
-	return tocLen
-}
diff --git a/cmd/golangorg/godoc/server.go b/cmd/golangorg/godoc/server.go
deleted file mode 100644
index 5494fe4..0000000
--- a/cmd/golangorg/godoc/server.go
+++ /dev/null
@@ -1,836 +0,0 @@
-// 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.
-
-package godoc
-
-import (
-	"bytes"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"go/ast"
-	"go/build"
-	"go/doc"
-	"go/token"
-	htmlpkg "html"
-	htmltemplate "html/template"
-	"io"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	pathpkg "path"
-	"path/filepath"
-	"sort"
-	"strings"
-	"text/template"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/analysis"
-	"golang.org/x/website/cmd/golangorg/godoc/util"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// handlerServer is a migration from an old godoc http Handler type.
-// This should probably merge into something else.
-type handlerServer struct {
-	p           *Presentation
-	c           *Corpus  // copy of p.Corpus
-	pattern     string   // url pattern; e.g. "/pkg/"
-	stripPrefix string   // prefix to strip from import path; e.g. "pkg/"
-	fsRoot      string   // file system root to which the pattern is mapped; e.g. "/src"
-	exclude     []string // file system paths to exclude; e.g. "/src/cmd"
-}
-
-func (s *handlerServer) registerWithMux(mux *http.ServeMux) {
-	mux.Handle(s.pattern, s)
-}
-
-// getPageInfo returns the PageInfo for a package directory abspath. If the
-// parameter genAST is set, an AST containing only the package exports is
-// computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc)
-// is extracted from the AST. If there is no corresponding package in the
-// directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub-
-// directories, PageInfo.Dirs is nil. If an error occurred, PageInfo.Err is
-// set to the respective error but the error is not logged.
-//
-func (h *handlerServer) GetPageInfo(abspath, relpath string, mode PageInfoMode, goos, goarch string) *PageInfo {
-	info := &PageInfo{Dirname: abspath, Mode: mode}
-
-	// Restrict to the package files that would be used when building
-	// the package on this system.  This makes sure that if there are
-	// separate implementations for, say, Windows vs Unix, we don't
-	// jumble them all together.
-	// Note: If goos/goarch aren't set, the current binary's GOOS/GOARCH
-	// are used.
-	ctxt := build.Default
-	ctxt.IsAbsPath = pathpkg.IsAbs
-	ctxt.IsDir = func(path string) bool {
-		fi, err := h.c.fs.Stat(filepath.ToSlash(path))
-		return err == nil && fi.IsDir()
-	}
-	ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
-		f, err := h.c.fs.ReadDir(filepath.ToSlash(dir))
-		filtered := make([]os.FileInfo, 0, len(f))
-		for _, i := range f {
-			if mode&NoFiltering != 0 || i.Name() != "internal" {
-				filtered = append(filtered, i)
-			}
-		}
-		return filtered, err
-	}
-	ctxt.OpenFile = func(name string) (r io.ReadCloser, err error) {
-		data, err := vfs.ReadFile(h.c.fs, filepath.ToSlash(name))
-		if err != nil {
-			return nil, err
-		}
-		return ioutil.NopCloser(bytes.NewReader(data)), nil
-	}
-
-	// Make the syscall/js package always visible by default.
-	// It defaults to the host's GOOS/GOARCH, and golang.org's
-	// linux/amd64 means the wasm syscall/js package was blank.
-	// And you can't run godoc on js/wasm anyway, so host defaults
-	// don't make sense here.
-	if goos == "" && goarch == "" && relpath == "syscall/js" {
-		goos, goarch = "js", "wasm"
-	}
-	if goos != "" {
-		ctxt.GOOS = goos
-	}
-	if goarch != "" {
-		ctxt.GOARCH = goarch
-	}
-
-	pkginfo, err := ctxt.ImportDir(abspath, 0)
-	// continue if there are no Go source files; we still want the directory info
-	if _, nogo := err.(*build.NoGoError); err != nil && !nogo {
-		info.Err = err
-		return info
-	}
-
-	// collect package files
-	pkgname := pkginfo.Name
-	pkgfiles := append(pkginfo.GoFiles, pkginfo.CgoFiles...)
-	if len(pkgfiles) == 0 {
-		// Commands written in C have no .go files in the build.
-		// Instead, documentation may be found in an ignored file.
-		// The file may be ignored via an explicit +build ignore
-		// constraint (recommended), or by defining the package
-		// documentation (historic).
-		pkgname = "main" // assume package main since pkginfo.Name == ""
-		pkgfiles = pkginfo.IgnoredGoFiles
-	}
-
-	// get package information, if any
-	if len(pkgfiles) > 0 {
-		// build package AST
-		fset := token.NewFileSet()
-		files, err := h.c.parseFiles(fset, relpath, abspath, pkgfiles)
-		if err != nil {
-			info.Err = err
-			return info
-		}
-
-		// ignore any errors - they are due to unresolved identifiers
-		pkg, _ := ast.NewPackage(fset, files, poorMansImporter, nil)
-
-		// extract package documentation
-		info.FSet = fset
-		if mode&ShowSource == 0 {
-			// show extracted documentation
-			var m doc.Mode
-			if mode&NoFiltering != 0 {
-				m |= doc.AllDecls
-			}
-			if mode&AllMethods != 0 {
-				m |= doc.AllMethods
-			}
-			info.PDoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath
-			if mode&NoTypeAssoc != 0 {
-				for _, t := range info.PDoc.Types {
-					info.PDoc.Consts = append(info.PDoc.Consts, t.Consts...)
-					info.PDoc.Vars = append(info.PDoc.Vars, t.Vars...)
-					info.PDoc.Funcs = append(info.PDoc.Funcs, t.Funcs...)
-					t.Consts = nil
-					t.Vars = nil
-					t.Funcs = nil
-				}
-				// for now we cannot easily sort consts and vars since
-				// go/doc.Value doesn't export the order information
-				sort.Sort(funcsByName(info.PDoc.Funcs))
-			}
-
-			// collect examples
-			testfiles := append(pkginfo.TestGoFiles, pkginfo.XTestGoFiles...)
-			files, err = h.c.parseFiles(fset, relpath, abspath, testfiles)
-			if err != nil {
-				log.Println("parsing examples:", err)
-			}
-			info.Examples = collectExamples(h.c, pkg, files)
-
-			// collect any notes that we want to show
-			if info.PDoc.Notes != nil {
-				// could regexp.Compile only once per godoc, but probably not worth it
-				if rx := h.p.NotesRx; rx != nil {
-					for m, n := range info.PDoc.Notes {
-						if rx.MatchString(m) {
-							if info.Notes == nil {
-								info.Notes = make(map[string][]*doc.Note)
-							}
-							info.Notes[m] = n
-						}
-					}
-				}
-			}
-
-		} else {
-			// show source code
-			// TODO(gri) Consider eliminating export filtering in this mode,
-			//           or perhaps eliminating the mode altogether.
-			if mode&NoFiltering == 0 {
-				packageExports(fset, pkg)
-			}
-			info.PAst = files
-		}
-		info.IsMain = pkgname == "main"
-	}
-
-	// get directory information, if any
-	var dir *Directory
-	var timestamp time.Time
-	if tree, ts := h.c.fsTree.Get(); tree != nil && tree.(*Directory) != nil {
-		// directory tree is present; lookup respective directory
-		// (may still fail if the file system was updated and the
-		// new directory tree has not yet been computed)
-		dir = tree.(*Directory).lookup(abspath)
-		timestamp = ts
-	}
-	if dir == nil {
-		// TODO(agnivade): handle this case better, now since there is no CLI mode.
-		// no directory tree present (happens in command-line mode);
-		// compute 2 levels for this page. The second level is to
-		// get the synopses of sub-directories.
-		// note: cannot use path filter here because in general
-		// it doesn't contain the FSTree path
-		dir = h.c.newDirectory(abspath, 2)
-		timestamp = time.Now()
-	}
-	info.Dirs = dir.listing(true, func(path string) bool { return h.includePath(path, mode) })
-
-	info.DirTime = timestamp
-	info.DirFlat = mode&FlatDir != 0
-
-	return info
-}
-
-func (h *handlerServer) includePath(path string, mode PageInfoMode) (r bool) {
-	// if the path is under one of the exclusion paths, don't list.
-	for _, e := range h.exclude {
-		if strings.HasPrefix(path, e) {
-			return false
-		}
-	}
-
-	// if the path includes 'internal', don't list unless we are in the NoFiltering mode.
-	if mode&NoFiltering != 0 {
-		return true
-	}
-	if strings.Contains(path, "internal") || strings.Contains(path, "vendor") {
-		for _, c := range strings.Split(filepath.Clean(path), string(os.PathSeparator)) {
-			if c == "internal" || c == "vendor" {
-				return false
-			}
-		}
-	}
-	return true
-}
-
-type funcsByName []*doc.Func
-
-func (s funcsByName) Len() int           { return len(s) }
-func (s funcsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
-func (s funcsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
-
-func (h *handlerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	if redirect(w, r) {
-		return
-	}
-
-	relpath := pathpkg.Clean(r.URL.Path[len(h.stripPrefix)+1:])
-
-	if !h.corpusInitialized() {
-		h.p.ServeError(w, r, relpath, errors.New("Scan is not yet complete. Please retry after a few moments"))
-		return
-	}
-
-	abspath := pathpkg.Join(h.fsRoot, relpath)
-	mode := h.p.GetPageInfoMode(r)
-	if relpath == builtinPkgPath {
-		mode = NoFiltering | NoTypeAssoc
-	}
-	info := h.GetPageInfo(abspath, relpath, mode, r.FormValue("GOOS"), r.FormValue("GOARCH"))
-	if info.Err != nil {
-		log.Print(info.Err)
-		h.p.ServeError(w, r, relpath, info.Err)
-		return
-	}
-
-	var tabtitle, title, subtitle string
-	switch {
-	case info.PAst != nil:
-		for _, ast := range info.PAst {
-			tabtitle = ast.Name.Name
-			break
-		}
-	case info.PDoc != nil:
-		tabtitle = info.PDoc.Name
-	default:
-		tabtitle = info.Dirname
-		title = "Directory "
-		if h.p.ShowTimestamps {
-			subtitle = "Last update: " + info.DirTime.String()
-		}
-	}
-	if title == "" {
-		if info.IsMain {
-			// assume that the directory name is the command name
-			_, tabtitle = pathpkg.Split(relpath)
-			title = "Command "
-		} else {
-			title = "Package "
-		}
-	}
-	title += tabtitle
-
-	// special cases for top-level package/command directories
-	switch tabtitle {
-	case "/src":
-		title = "Packages"
-		tabtitle = "Packages"
-	case "/src/cmd":
-		title = "Commands"
-		tabtitle = "Commands"
-	}
-
-	// Emit JSON array for type information.
-	pi := h.c.Analysis.PackageInfo(relpath)
-	hasTreeView := len(pi.CallGraph) != 0
-	info.CallGraphIndex = pi.CallGraphIndex
-	info.CallGraph = htmltemplate.JS(marshalJSON(pi.CallGraph))
-	info.AnalysisData = htmltemplate.JS(marshalJSON(pi.Types))
-	info.TypeInfoIndex = make(map[string]int)
-	for i, ti := range pi.Types {
-		info.TypeInfoIndex[ti.Name] = i
-	}
-
-	info.GoogleCN = googleCN(r)
-	var body []byte
-	if info.Dirname == "/src" {
-		body = applyTemplate(h.p.PackageRootHTML, "packageRootHTML", info)
-	} else {
-		body = applyTemplate(h.p.PackageHTML, "packageHTML", info)
-	}
-	h.p.ServePage(w, Page{
-		Title:    title,
-		Tabtitle: tabtitle,
-		Subtitle: subtitle,
-		Body:     body,
-		GoogleCN: info.GoogleCN,
-		TreeView: hasTreeView,
-	})
-}
-
-func (h *handlerServer) corpusInitialized() bool {
-	h.c.initMu.RLock()
-	defer h.c.initMu.RUnlock()
-	return h.c.initDone
-}
-
-type PageInfoMode uint
-
-const (
-	PageInfoModeQueryString = "m" // query string where PageInfoMode is stored
-
-	NoFiltering PageInfoMode = 1 << iota // do not filter exports
-	AllMethods                           // show all embedded methods
-	ShowSource                           // show source code, do not extract documentation
-	NoHTML                               // show result in textual form, do not generate HTML
-	FlatDir                              // show directory in a flat (non-indented) manner
-	NoTypeAssoc                          // don't associate consts, vars, and factory functions with types
-)
-
-// modeNames defines names for each PageInfoMode flag.
-var modeNames = map[string]PageInfoMode{
-	"all":     NoFiltering,
-	"methods": AllMethods,
-	"src":     ShowSource,
-	"text":    NoHTML,
-	"flat":    FlatDir,
-}
-
-// generate a query string for persisting PageInfoMode between pages.
-func modeQueryString(mode PageInfoMode) string {
-	if modeNames := mode.names(); len(modeNames) > 0 {
-		return "?m=" + strings.Join(modeNames, ",")
-	}
-	return ""
-}
-
-// alphabetically sorted names of active flags for a PageInfoMode.
-func (m PageInfoMode) names() []string {
-	var names []string
-	for name, mode := range modeNames {
-		if m&mode != 0 {
-			names = append(names, name)
-		}
-	}
-	sort.Strings(names)
-	return names
-}
-
-// GetPageInfoMode computes the PageInfoMode flags by analyzing the request
-// URL form value "m". It is value is a comma-separated list of mode names
-// as defined by modeNames (e.g.: m=src,text).
-func (p *Presentation) GetPageInfoMode(r *http.Request) PageInfoMode {
-	var mode PageInfoMode
-	for _, k := range strings.Split(r.FormValue(PageInfoModeQueryString), ",") {
-		if m, found := modeNames[strings.TrimSpace(k)]; found {
-			mode |= m
-		}
-	}
-	if p.AdjustPageInfoMode != nil {
-		mode = p.AdjustPageInfoMode(r, mode)
-	}
-	return mode
-}
-
-// poorMansImporter returns a (dummy) package object named
-// by the last path component of the provided package path
-// (as is the convention for packages). This is sufficient
-// to resolve package identifiers without doing an actual
-// import. It never returns an error.
-//
-func poorMansImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
-	pkg := imports[path]
-	if pkg == nil {
-		// note that strings.LastIndex returns -1 if there is no "/"
-		pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
-		pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
-		imports[path] = pkg
-	}
-	return pkg, nil
-}
-
-// globalNames returns a set of the names declared by all package-level
-// declarations. Method names are returned in the form Receiver_Method.
-func globalNames(pkg *ast.Package) map[string]bool {
-	names := make(map[string]bool)
-	for _, file := range pkg.Files {
-		for _, decl := range file.Decls {
-			addNames(names, decl)
-		}
-	}
-	return names
-}
-
-// collectExamples collects examples for pkg from testfiles.
-func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example {
-	var files []*ast.File
-	for _, f := range testfiles {
-		files = append(files, f)
-	}
-
-	var examples []*doc.Example
-	globals := globalNames(pkg)
-	for _, e := range doc.Examples(files...) {
-		name := stripExampleSuffix(e.Name)
-		if name == "" || globals[name] {
-			examples = append(examples, e)
-		} else if c.Verbose {
-			log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name)
-		}
-	}
-
-	return examples
-}
-
-// addNames adds the names declared by decl to the names set.
-// Method names are added in the form ReceiverTypeName_Method.
-func addNames(names map[string]bool, decl ast.Decl) {
-	switch d := decl.(type) {
-	case *ast.FuncDecl:
-		name := d.Name.Name
-		if d.Recv != nil {
-			var typeName string
-			switch r := d.Recv.List[0].Type.(type) {
-			case *ast.StarExpr:
-				typeName = r.X.(*ast.Ident).Name
-			case *ast.Ident:
-				typeName = r.Name
-			}
-			name = typeName + "_" + name
-		}
-		names[name] = true
-	case *ast.GenDecl:
-		for _, spec := range d.Specs {
-			switch s := spec.(type) {
-			case *ast.TypeSpec:
-				names[s.Name.Name] = true
-			case *ast.ValueSpec:
-				for _, id := range s.Names {
-					names[id.Name] = true
-				}
-			}
-		}
-	}
-}
-
-// packageExports is a local implementation of ast.PackageExports
-// which correctly updates each package file's comment list.
-// (The ast.PackageExports signature is frozen, hence the local
-// implementation).
-//
-func packageExports(fset *token.FileSet, pkg *ast.Package) {
-	for _, src := range pkg.Files {
-		cmap := ast.NewCommentMap(fset, src, src.Comments)
-		ast.FileExports(src)
-		src.Comments = cmap.Filter(src).Comments()
-	}
-}
-
-func applyTemplate(t *template.Template, name string, data interface{}) []byte {
-	var buf bytes.Buffer
-	if err := t.Execute(&buf, data); err != nil {
-		log.Printf("%s.Execute: %s", name, err)
-	}
-	return buf.Bytes()
-}
-
-type writerCapturesErr struct {
-	w   io.Writer
-	err error
-}
-
-func (w *writerCapturesErr) Write(p []byte) (int, error) {
-	n, err := w.w.Write(p)
-	if err != nil {
-		w.err = err
-	}
-	return n, err
-}
-
-// applyTemplateToResponseWriter uses an http.ResponseWriter as the io.Writer
-// for the call to template.Execute.  It uses an io.Writer wrapper to capture
-// errors from the underlying http.ResponseWriter.  Errors are logged only when
-// they come from the template processing and not the Writer; this avoid
-// polluting log files with error messages due to networking issues, such as
-// client disconnects and http HEAD protocol violations.
-func applyTemplateToResponseWriter(rw http.ResponseWriter, t *template.Template, data interface{}) {
-	w := &writerCapturesErr{w: rw}
-	err := t.Execute(w, data)
-	// There are some cases where template.Execute does not return an error when
-	// rw returns an error, and some where it does.  So check w.err first.
-	if w.err == nil && err != nil {
-		// Log template errors.
-		log.Printf("%s.Execute: %s", t.Name(), err)
-	}
-}
-
-func redirect(w http.ResponseWriter, r *http.Request) (redirected bool) {
-	canonical := pathpkg.Clean(r.URL.Path)
-	if !strings.HasSuffix(canonical, "/") {
-		canonical += "/"
-	}
-	if r.URL.Path != canonical {
-		url := *r.URL
-		url.Path = canonical
-		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
-		redirected = true
-	}
-	return
-}
-
-func redirectFile(w http.ResponseWriter, r *http.Request) (redirected bool) {
-	c := pathpkg.Clean(r.URL.Path)
-	c = strings.TrimRight(c, "/")
-	if r.URL.Path != c {
-		url := *r.URL
-		url.Path = c
-		http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
-		redirected = true
-	}
-	return
-}
-
-func (p *Presentation) serveTextFile(w http.ResponseWriter, r *http.Request, abspath, relpath, title string) {
-	src, err := vfs.ReadFile(p.Corpus.fs, abspath)
-	if err != nil {
-		log.Printf("ReadFile: %s", err)
-		p.ServeError(w, r, relpath, err)
-		return
-	}
-
-	if r.FormValue(PageInfoModeQueryString) == "text" {
-		p.ServeText(w, src)
-		return
-	}
-
-	h := r.FormValue("h")
-	s := RangeSelection(r.FormValue("s"))
-
-	var buf bytes.Buffer
-	if pathpkg.Ext(abspath) == ".go" {
-		// Find markup links for this file (e.g. "/src/fmt/print.go").
-		fi := p.Corpus.Analysis.FileInfo(abspath)
-		buf.WriteString("<script type='text/javascript'>document.ANALYSIS_DATA = ")
-		buf.Write(marshalJSON(fi.Data))
-		buf.WriteString(";</script>\n")
-
-		if status := p.Corpus.Analysis.Status(); status != "" {
-			buf.WriteString("<a href='/lib/godoc/analysis/help.html'>Static analysis features</a> ")
-			// TODO(adonovan): show analysis status at per-file granularity.
-			fmt.Fprintf(&buf, "<span style='color: grey'>[%s]</span><br/>", htmlpkg.EscapeString(status))
-		}
-
-		buf.WriteString("<pre>")
-		formatGoSource(&buf, src, fi.Links, h, s)
-		buf.WriteString("</pre>")
-	} else {
-		buf.WriteString("<pre>")
-		FormatText(&buf, src, 1, false, h, s)
-		buf.WriteString("</pre>")
-	}
-	fmt.Fprintf(&buf, `<p><a href="/%s?m=text">View as plain text</a></p>`, htmlpkg.EscapeString(relpath))
-
-	p.ServePage(w, Page{
-		Title:    title,
-		SrcPath:  relpath,
-		Tabtitle: relpath,
-		Body:     buf.Bytes(),
-		GoogleCN: googleCN(r),
-	})
-}
-
-// formatGoSource HTML-escapes Go source text and writes it to w,
-// decorating it with the specified analysis links.
-//
-func formatGoSource(buf *bytes.Buffer, text []byte, links []analysis.Link, pattern string, selection Selection) {
-	// Emit to a temp buffer so that we can add line anchors at the end.
-	saved, buf := buf, new(bytes.Buffer)
-
-	var i int
-	var link analysis.Link // shared state of the two funcs below
-	segmentIter := func() (seg Segment) {
-		if i < len(links) {
-			link = links[i]
-			i++
-			seg = Segment{link.Start(), link.End()}
-		}
-		return
-	}
-	linkWriter := func(w io.Writer, offs int, start bool) {
-		link.Write(w, offs, start)
-	}
-
-	comments := tokenSelection(text, token.COMMENT)
-	var highlights Selection
-	if pattern != "" {
-		highlights = regexpSelection(text, pattern)
-	}
-
-	FormatSelections(buf, text, linkWriter, segmentIter, selectionTag, comments, highlights, selection)
-
-	// Now copy buf to saved, adding line anchors.
-
-	// The lineSelection mechanism can't be composed with our
-	// linkWriter, so we have to add line spans as another pass.
-	n := 1
-	for _, line := range bytes.Split(buf.Bytes(), []byte("\n")) {
-		// The line numbers are inserted into the document via a CSS ::before
-		// pseudo-element. This prevents them from being copied when users
-		// highlight and copy text.
-		// ::before is supported in 98% of browsers: https://caniuse.com/#feat=css-gencontent
-		// This is also the trick Github uses to hide line numbers.
-		//
-		// The first tab for the code snippet needs to start in column 9, so
-		// it indents a full 8 spaces, hence the two nbsp's. Otherwise the tab
-		// character only indents a short amount.
-		//
-		// Due to rounding and font width Firefox might not treat 8 rendered
-		// characters as 8 characters wide, and subsequently may treat the tab
-		// character in the 9th position as moving the width from (7.5 or so) up
-		// to 8. See
-		// https://github.com/webcompat/web-bugs/issues/17530#issuecomment-402675091
-		// for a fuller explanation. The solution is to add a CSS class to
-		// explicitly declare the width to be 8 characters.
-		fmt.Fprintf(saved, `<span id="L%d" class="ln">%6d&nbsp;&nbsp;</span>`, n, n)
-		n++
-		saved.Write(line)
-		saved.WriteByte('\n')
-	}
-}
-
-func (p *Presentation) serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
-	if redirect(w, r) {
-		return
-	}
-
-	list, err := p.Corpus.fs.ReadDir(abspath)
-	if err != nil {
-		p.ServeError(w, r, relpath, err)
-		return
-	}
-
-	p.ServePage(w, Page{
-		Title:    "Directory",
-		SrcPath:  relpath,
-		Tabtitle: relpath,
-		Body:     applyTemplate(p.DirlistHTML, "dirlistHTML", list),
-		GoogleCN: googleCN(r),
-	})
-}
-
-func (p *Presentation) ServeHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) {
-	// get HTML body contents
-	src, err := vfs.ReadFile(p.Corpus.fs, abspath)
-	if err != nil {
-		log.Printf("ReadFile: %s", err)
-		p.ServeError(w, r, relpath, err)
-		return
-	}
-
-	// if it begins with "<!DOCTYPE " assume it is standalone
-	// html that doesn't need the template wrapping.
-	if bytes.HasPrefix(src, doctype) {
-		w.Write(src)
-		return
-	}
-
-	// if it begins with a JSON blob, read in the metadata.
-	meta, src, err := extractMetadata(src)
-	if err != nil {
-		log.Printf("decoding metadata %s: %v", relpath, err)
-	}
-
-	page := Page{
-		Title:    meta.Title,
-		Subtitle: meta.Subtitle,
-		GoogleCN: googleCN(r),
-	}
-
-	// evaluate as template if indicated
-	if meta.Template {
-		tmpl, err := template.New("main").Funcs(p.TemplateFuncs()).Parse(string(src))
-		if err != nil {
-			log.Printf("parsing template %s: %v", relpath, err)
-			p.ServeError(w, r, relpath, err)
-			return
-		}
-		var buf bytes.Buffer
-		if err := tmpl.Execute(&buf, page); err != nil {
-			log.Printf("executing template %s: %v", relpath, err)
-			p.ServeError(w, r, relpath, err)
-			return
-		}
-		src = buf.Bytes()
-	}
-
-	// if it's the language spec, add tags to EBNF productions
-	if strings.HasSuffix(abspath, "go_spec.html") {
-		var buf bytes.Buffer
-		Linkify(&buf, src)
-		src = buf.Bytes()
-	}
-
-	page.Body = src
-	p.ServePage(w, page)
-}
-
-func (p *Presentation) ServeFile(w http.ResponseWriter, r *http.Request) {
-	p.serveFile(w, r)
-}
-
-func (p *Presentation) serveFile(w http.ResponseWriter, r *http.Request) {
-	relpath := r.URL.Path
-
-	// Check to see if we need to redirect or serve another file.
-	if m := p.Corpus.MetadataFor(relpath); m != nil {
-		if m.Path != relpath {
-			// Redirect to canonical path.
-			http.Redirect(w, r, m.Path, http.StatusMovedPermanently)
-			return
-		}
-		// Serve from the actual filesystem path.
-		relpath = m.filePath
-	}
-
-	abspath := relpath
-	relpath = relpath[1:] // strip leading slash
-
-	switch pathpkg.Ext(relpath) {
-	case ".html":
-		if strings.HasSuffix(relpath, "/index.html") {
-			// We'll show index.html for the directory.
-			// Use the dir/ version as canonical instead of dir/index.html.
-			http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently)
-			return
-		}
-		p.ServeHTMLDoc(w, r, abspath, relpath)
-		return
-
-	case ".go":
-		p.serveTextFile(w, r, abspath, relpath, "Source file")
-		return
-	}
-
-	dir, err := p.Corpus.fs.Lstat(abspath)
-	if err != nil {
-		log.Print(err)
-		p.ServeError(w, r, relpath, err)
-		return
-	}
-
-	if dir != nil && dir.IsDir() {
-		if redirect(w, r) {
-			return
-		}
-		if index := pathpkg.Join(abspath, "index.html"); util.IsTextFile(p.Corpus.fs, index) {
-			p.ServeHTMLDoc(w, r, index, index)
-			return
-		}
-		p.serveDirectory(w, r, abspath, relpath)
-		return
-	}
-
-	if util.IsTextFile(p.Corpus.fs, abspath) {
-		if redirectFile(w, r) {
-			return
-		}
-		p.serveTextFile(w, r, abspath, relpath, "Text file")
-		return
-	}
-
-	p.fileServer.ServeHTTP(w, r)
-}
-
-func (p *Presentation) ServeText(w http.ResponseWriter, text []byte) {
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.Write(text)
-}
-
-func marshalJSON(x interface{}) []byte {
-	var data []byte
-	var err error
-	const indentJSON = false // for easier debugging
-	if indentJSON {
-		data, err = json.MarshalIndent(x, "", "    ")
-	} else {
-		data, err = json.Marshal(x)
-	}
-	if err != nil {
-		panic(fmt.Sprintf("json.Marshal failed: %s", err))
-	}
-	return data
-}
diff --git a/cmd/golangorg/godoc/server_test.go b/cmd/golangorg/godoc/server_test.go
deleted file mode 100644
index 4419759..0000000
--- a/cmd/golangorg/godoc/server_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2018 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.
-
-package godoc
-
-import (
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/mapfs"
-)
-
-// TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files,
-// but has an ignored go file.
-func TestIgnoredGoFiles(t *testing.T) {
-	packagePath := "github.com/package"
-	packageComment := "main is documented in an ignored .go file"
-
-	c := NewCorpus(mapfs.New(map[string]string{
-		"src/" + packagePath + "/ignored.go": `// +build ignore
-
-// ` + packageComment + `
-package main`}))
-	srv := &handlerServer{
-		p: &Presentation{
-			Corpus: c,
-		},
-		c: c,
-	}
-	pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64")
-
-	if pInfo.PDoc == nil {
-		t.Error("pInfo.PDoc = nil; want non-nil.")
-	} else {
-		if got, want := pInfo.PDoc.Doc, packageComment+"\n"; got != want {
-			t.Errorf("pInfo.PDoc.Doc = %q; want %q.", got, want)
-		}
-		if got, want := pInfo.PDoc.Name, "main"; got != want {
-			t.Errorf("pInfo.PDoc.Name = %q; want %q.", got, want)
-		}
-		if got, want := pInfo.PDoc.ImportPath, packagePath; got != want {
-			t.Errorf("pInfo.PDoc.ImportPath = %q; want %q.", got, want)
-		}
-	}
-	if pInfo.FSet == nil {
-		t.Error("pInfo.FSet = nil; want non-nil.")
-	}
-}
diff --git a/cmd/golangorg/godoc/short/short.go b/cmd/golangorg/godoc/short/short.go
deleted file mode 100644
index 69e82dc..0000000
--- a/cmd/golangorg/godoc/short/short.go
+++ /dev/null
@@ -1,186 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-// +build golangorg
-
-// Package short implements a simple URL shortener, serving an administrative
-// interface at /s and shortened urls from /s/key.
-// It is designed to run only on the instance of godoc that serves golang.org.
-package short
-
-// TODO(adg): collect statistics on URL visits
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"html/template"
-	"io"
-	"log"
-	"net/http"
-	"net/url"
-	"regexp"
-
-	"cloud.google.com/go/datastore"
-	"golang.org/x/website/internal/memcache"
-	"google.golang.org/appengine/user"
-)
-
-const (
-	prefix  = "/s"
-	kind    = "Link"
-	baseURL = "https://golang.org" + prefix
-)
-
-// Link represents a short link.
-type Link struct {
-	Key, Target string
-}
-
-var validKey = regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`)
-
-type server struct {
-	datastore *datastore.Client
-	memcache  *memcache.CodecClient
-}
-
-func RegisterHandlers(mux *http.ServeMux, dc *datastore.Client, mc *memcache.Client) {
-	s := server{dc, mc.WithCodec(memcache.JSON)}
-	mux.HandleFunc(prefix+"/", s.linkHandler)
-
-	// TODO(cbro): move storage of the links to a text file in Gerrit.
-	// Disable the admin handler until that happens, since GAE Flex doesn't support
-	// the "google.golang.org/appengine/user" package.
-	// See golang.org/issue/27205#issuecomment-418673218
-	// mux.HandleFunc(prefix, adminHandler)
-	mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusForbidden)
-		io.WriteString(w, "Link creation temporarily unavailable. See golang.org/issue/27205.")
-	})
-}
-
-// linkHandler services requests to short URLs.
-//   http://golang.org/s/key
-// It consults memcache and datastore for the Link for key.
-// It then sends a redirects or an error message.
-func (h server) linkHandler(w http.ResponseWriter, r *http.Request) {
-	ctx := r.Context()
-
-	key := r.URL.Path[len(prefix)+1:]
-	if !validKey.MatchString(key) {
-		http.Error(w, "not found", http.StatusNotFound)
-		return
-	}
-
-	var link Link
-	if err := h.memcache.Get(ctx, cacheKey(key), &link); err != nil {
-		k := datastore.NameKey(kind, key, nil)
-		err = h.datastore.Get(ctx, k, &link)
-		switch err {
-		case datastore.ErrNoSuchEntity:
-			http.Error(w, "not found", http.StatusNotFound)
-			return
-		default: // != nil
-			log.Printf("ERROR %q: %v", key, err)
-			http.Error(w, "internal server error", http.StatusInternalServerError)
-			return
-		case nil:
-			item := &memcache.Item{
-				Key:    cacheKey(key),
-				Object: &link,
-			}
-			if err := h.memcache.Set(ctx, item); err != nil {
-				log.Printf("WARNING %q: %v", key, err)
-			}
-		}
-	}
-
-	http.Redirect(w, r, link.Target, http.StatusFound)
-}
-
-var adminTemplate = template.Must(template.New("admin").Parse(templateHTML))
-
-// adminHandler serves an administrative interface.
-func (h server) adminHandler(w http.ResponseWriter, r *http.Request) {
-	ctx := r.Context()
-
-	if !user.IsAdmin(ctx) {
-		http.Error(w, "forbidden", http.StatusForbidden)
-		return
-	}
-
-	var newLink *Link
-	var doErr error
-	if r.Method == "POST" {
-		key := r.FormValue("key")
-		switch r.FormValue("do") {
-		case "Add":
-			newLink = &Link{key, r.FormValue("target")}
-			doErr = h.putLink(ctx, newLink)
-		case "Delete":
-			k := datastore.NameKey(kind, key, nil)
-			doErr = h.datastore.Delete(ctx, k)
-		default:
-			http.Error(w, "unknown action", http.StatusBadRequest)
-		}
-		err := h.memcache.Delete(ctx, cacheKey(key))
-		if err != nil && err != memcache.ErrCacheMiss {
-			log.Printf("WARNING %q: %v", key, err)
-		}
-	}
-
-	var links []*Link
-	q := datastore.NewQuery(kind).Order("Key")
-	if _, err := h.datastore.GetAll(ctx, q, &links); err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-		log.Printf("ERROR %v", err)
-		return
-	}
-
-	// Put the new link in the list if it's not there already.
-	// (Eventual consistency means that it might not show up
-	// immediately, which might be confusing for the user.)
-	if newLink != nil && doErr == nil {
-		found := false
-		for i := range links {
-			if links[i].Key == newLink.Key {
-				found = true
-				break
-			}
-		}
-		if !found {
-			links = append([]*Link{newLink}, links...)
-		}
-		newLink = nil
-	}
-
-	var data = struct {
-		BaseURL string
-		Prefix  string
-		Links   []*Link
-		New     *Link
-		Error   error
-	}{baseURL, prefix, links, newLink, doErr}
-	if err := adminTemplate.Execute(w, &data); err != nil {
-		log.Printf("ERROR adminTemplate: %v", err)
-	}
-}
-
-// putLink validates the provided link and puts it into the datastore.
-func (h server) putLink(ctx context.Context, link *Link) error {
-	if !validKey.MatchString(link.Key) {
-		return errors.New("invalid key; must match " + validKey.String())
-	}
-	if _, err := url.Parse(link.Target); err != nil {
-		return fmt.Errorf("bad target: %v", err)
-	}
-	k := datastore.NameKey(kind, link.Key, nil)
-	_, err := h.datastore.Put(ctx, k, link)
-	return err
-}
-
-// cacheKey returns a short URL key as a memcache key.
-func cacheKey(key string) string {
-	return "link-" + key
-}
diff --git a/cmd/golangorg/godoc/short/tmpl.go b/cmd/golangorg/godoc/short/tmpl.go
deleted file mode 100644
index 66f5401..0000000
--- a/cmd/golangorg/godoc/short/tmpl.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by the Apache 2.0
-// license that can be found in the LICENSE file.
-
-package short
-
-const templateHTML = `
-<!doctype HTML>
-<html>
-<head>
-<title>golang.org URL shortener</title>
-<style>
-body {
-	background: white;
-}
-input {
-	border: 1px solid #ccc;
-}
-input[type=text] {
-	width: 400px;
-}
-input, td, th {
-	color: #333;
-	font-family: Georgia, Times New Roman, serif;
-}
-input, td {
-	font-size: 14pt;
-}
-th {
-	font-size: 16pt;
-	text-align: left;
-	padding-top: 10px;
-}
-.autoselect {
-	border: none;
-}
-.error {
-	color: #900;
-}
-table {
-	margin-left: auto;
-	margin-right: auto;
-}
-</style>
-</head>
-<body>
-
-<table>
-
-{{with .Error}}
-<tr>
-	<th colspan="3">Error</th>
-</tr>
-<tr>
-	<td class="error" colspan="3">{{.}}</td>
-</tr>
-{{end}}
-
-<tr>
-	<th>Key</th>
-	<th>Target</th>
-	<th></th>
-</tr>
-
-<form method="POST" action="{{.Prefix}}">
-<tr>
-	<td><input type="text" name="key"{{with .New}} value="{{.Key}}"{{end}}></td>
-	<td><input type="text" name="target"{{with .New}} value="{{.Target}}"{{end}}></td>
-	<td><input type="submit" name="do" value="Add">
-</tr>
-</form>
-
-{{with .Links}}
-<tr>
-	<th>Short Link</th>
-	<th>&nbsp;</th>
-	<th>&nbsp;</th>
-</tr>
-{{range .}}
-<tr>
-	<td><input class="autoselect" type="text" orig="{{$.BaseURL}}/{{.Key}}" value="{{$.BaseURL}}/{{.Key}}"></td>
-	<td><input class="autoselect" type="text" orig="{{.Target}}" value="{{.Target}}"></td>
-	<td>
-		<form method="POST" action="{{$.Prefix}}">
-			<input type="hidden" name="key" value="{{.Key}}">
-			<input type="submit" name="do" value="Delete" class="delete">
-		</form>
-	</td>
-</tr>
-{{end}}
-{{end}}
-
-</table>
-
-</body>
-<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
-<script type="text/javascript">window.jQuery || document.write(unescape("%3Cscript src='/doc/jquery.js' type='text/javascript'%3E%3C/script%3E"));</script>
-<script>
-$(document).ready(function() {
-	$('.autoselect').each(function() {
-		$(this).click(function() {
-			$(this).select();
-		});
-		$(this).change(function() {
-			$(this).val($(this).attr('orig'));
-		});
-	});
-	$('.delete').click(function(e) {
-		var link = $(this).closest('tr').find('input').first().val();
-		var ok = confirm('Delete this link?\n' + link);
-		if (!ok) {
-			e.preventDefault();
-			return false;
-		}
-	});
-});
-</script>
-</html>
-`
diff --git a/cmd/golangorg/godoc/snippet.go b/cmd/golangorg/godoc/snippet.go
deleted file mode 100644
index 1750478..0000000
--- a/cmd/golangorg/godoc/snippet.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2009 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 contains the infrastructure to create a code
-// snippet for search results.
-//
-// Note: At the moment, this only creates HTML snippets.
-
-package godoc
-
-import (
-	"bytes"
-	"fmt"
-	"go/ast"
-	"go/token"
-)
-
-type Snippet struct {
-	Line int
-	Text string // HTML-escaped
-}
-
-func (p *Presentation) newSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
-	// TODO instead of pretty-printing the node, should use the original source instead
-	var buf1 bytes.Buffer
-	p.writeNode(&buf1, nil, fset, decl)
-	// wrap text with <pre> tag
-	var buf2 bytes.Buffer
-	buf2.WriteString("<pre>")
-	FormatText(&buf2, buf1.Bytes(), -1, true, id.Name, nil)
-	buf2.WriteString("</pre>")
-	return &Snippet{fset.Position(id.Pos()).Line, buf2.String()}
-}
-
-func findSpec(list []ast.Spec, id *ast.Ident) ast.Spec {
-	for _, spec := range list {
-		switch s := spec.(type) {
-		case *ast.ImportSpec:
-			if s.Name == id {
-				return s
-			}
-		case *ast.ValueSpec:
-			for _, n := range s.Names {
-				if n == id {
-					return s
-				}
-			}
-		case *ast.TypeSpec:
-			if s.Name == id {
-				return s
-			}
-		}
-	}
-	return nil
-}
-
-func (p *Presentation) genSnippet(fset *token.FileSet, d *ast.GenDecl, id *ast.Ident) *Snippet {
-	s := findSpec(d.Specs, id)
-	if s == nil {
-		return nil //  declaration doesn't contain id - exit gracefully
-	}
-
-	// only use the spec containing the id for the snippet
-	dd := &ast.GenDecl{
-		Doc:    d.Doc,
-		TokPos: d.Pos(),
-		Tok:    d.Tok,
-		Lparen: d.Lparen,
-		Specs:  []ast.Spec{s},
-		Rparen: d.Rparen,
-	}
-
-	return p.newSnippet(fset, dd, id)
-}
-
-func (p *Presentation) funcSnippet(fset *token.FileSet, d *ast.FuncDecl, id *ast.Ident) *Snippet {
-	if d.Name != id {
-		return nil //  declaration doesn't contain id - exit gracefully
-	}
-
-	// only use the function signature for the snippet
-	dd := &ast.FuncDecl{
-		Doc:  d.Doc,
-		Recv: d.Recv,
-		Name: d.Name,
-		Type: d.Type,
-	}
-
-	return p.newSnippet(fset, dd, id)
-}
-
-// NewSnippet creates a text snippet from a declaration decl containing an
-// identifier id. Parts of the declaration not containing the identifier
-// may be removed for a more compact snippet.
-func NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
-	// TODO(bradfitz, adg): remove this function.  But it's used by indexer, which
-	// doesn't have a *Presentation, and NewSnippet needs a TabWidth.
-	var p Presentation
-	p.TabWidth = 4
-	return p.NewSnippet(fset, decl, id)
-}
-
-// NewSnippet creates a text snippet from a declaration decl containing an
-// identifier id. Parts of the declaration not containing the identifier
-// may be removed for a more compact snippet.
-func (p *Presentation) NewSnippet(fset *token.FileSet, decl ast.Decl, id *ast.Ident) *Snippet {
-	var s *Snippet
-	switch d := decl.(type) {
-	case *ast.GenDecl:
-		s = p.genSnippet(fset, d, id)
-	case *ast.FuncDecl:
-		s = p.funcSnippet(fset, d, id)
-	}
-
-	// handle failure gracefully
-	if s == nil {
-		var buf bytes.Buffer
-		fmt.Fprintf(&buf, `<span class="alert">could not generate a snippet for <span class="highlight">%s</span></span>`, id.Name)
-		s = &Snippet{fset.Position(id.Pos()).Line, buf.String()}
-	}
-	return s
-}
diff --git a/cmd/golangorg/godoc/spec.go b/cmd/golangorg/godoc/spec.go
deleted file mode 100644
index 9ec9427..0000000
--- a/cmd/golangorg/godoc/spec.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2009 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.
-
-package godoc
-
-// This file contains the mechanism to "linkify" html source
-// text containing EBNF sections (as found in go_spec.html).
-// The result is the input source text with the EBNF sections
-// modified such that identifiers are linked to the respective
-// definitions.
-
-import (
-	"bytes"
-	"fmt"
-	"io"
-	"text/scanner"
-)
-
-type ebnfParser struct {
-	out     io.Writer // parser output
-	src     []byte    // parser input
-	scanner scanner.Scanner
-	prev    int    // offset of previous token
-	pos     int    // offset of current token
-	tok     rune   // one token look-ahead
-	lit     string // token literal
-}
-
-func (p *ebnfParser) flush() {
-	p.out.Write(p.src[p.prev:p.pos])
-	p.prev = p.pos
-}
-
-func (p *ebnfParser) next() {
-	p.tok = p.scanner.Scan()
-	p.pos = p.scanner.Position.Offset
-	p.lit = p.scanner.TokenText()
-}
-
-func (p *ebnfParser) printf(format string, args ...interface{}) {
-	p.flush()
-	fmt.Fprintf(p.out, format, args...)
-}
-
-func (p *ebnfParser) errorExpected(msg string) {
-	p.printf(`<span class="highlight">error: expected %s, found %s</span>`, msg, scanner.TokenString(p.tok))
-}
-
-func (p *ebnfParser) expect(tok rune) {
-	if p.tok != tok {
-		p.errorExpected(scanner.TokenString(tok))
-	}
-	p.next() // make progress in any case
-}
-
-func (p *ebnfParser) parseIdentifier(def bool) {
-	if p.tok == scanner.Ident {
-		name := p.lit
-		if def {
-			p.printf(`<a id="%s">%s</a>`, name, name)
-		} else {
-			p.printf(`<a href="#%s" class="noline">%s</a>`, name, name)
-		}
-		p.prev += len(name) // skip identifier when printing next time
-		p.next()
-	} else {
-		p.expect(scanner.Ident)
-	}
-}
-
-func (p *ebnfParser) parseTerm() bool {
-	switch p.tok {
-	case scanner.Ident:
-		p.parseIdentifier(false)
-
-	case scanner.String, scanner.RawString:
-		p.next()
-		const ellipsis = '…' // U+2026, the horizontal ellipsis character
-		if p.tok == ellipsis {
-			p.next()
-			p.expect(scanner.String)
-		}
-
-	case '(':
-		p.next()
-		p.parseExpression()
-		p.expect(')')
-
-	case '[':
-		p.next()
-		p.parseExpression()
-		p.expect(']')
-
-	case '{':
-		p.next()
-		p.parseExpression()
-		p.expect('}')
-
-	default:
-		return false // no term found
-	}
-
-	return true
-}
-
-func (p *ebnfParser) parseSequence() {
-	if !p.parseTerm() {
-		p.errorExpected("term")
-	}
-	for p.parseTerm() {
-	}
-}
-
-func (p *ebnfParser) parseExpression() {
-	for {
-		p.parseSequence()
-		if p.tok != '|' {
-			break
-		}
-		p.next()
-	}
-}
-
-func (p *ebnfParser) parseProduction() {
-	p.parseIdentifier(true)
-	p.expect('=')
-	if p.tok != '.' {
-		p.parseExpression()
-	}
-	p.expect('.')
-}
-
-func (p *ebnfParser) parse(out io.Writer, src []byte) {
-	// initialize ebnfParser
-	p.out = out
-	p.src = src
-	p.scanner.Init(bytes.NewBuffer(src))
-	p.next() // initializes pos, tok, lit
-
-	// process source
-	for p.tok != scanner.EOF {
-		p.parseProduction()
-	}
-	p.flush()
-}
-
-// Markers around EBNF sections
-var (
-	openTag  = []byte(`<pre class="ebnf">`)
-	closeTag = []byte(`</pre>`)
-)
-
-func Linkify(out io.Writer, src []byte) {
-	for len(src) > 0 {
-		// i: beginning of EBNF text (or end of source)
-		i := bytes.Index(src, openTag)
-		if i < 0 {
-			i = len(src) - len(openTag)
-		}
-		i += len(openTag)
-
-		// j: end of EBNF text (or end of source)
-		j := bytes.Index(src[i:], closeTag) // close marker
-		if j < 0 {
-			j = len(src) - i
-		}
-		j += i
-
-		// write text before EBNF
-		out.Write(src[0:i])
-		// process EBNF
-		var p ebnfParser
-		p.parse(out, src[i:j])
-
-		// advance
-		src = src[j:]
-	}
-}
diff --git a/cmd/golangorg/godoc/spec_test.go b/cmd/golangorg/godoc/spec_test.go
deleted file mode 100644
index c016516..0000000
--- a/cmd/golangorg/godoc/spec_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2018 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.
-
-package godoc
-
-import (
-	"bytes"
-	"strings"
-	"testing"
-)
-
-func TestParseEBNFString(t *testing.T) {
-	var p ebnfParser
-	var buf bytes.Buffer
-	src := []byte("octal_byte_value = `\\` octal_digit octal_digit octal_digit .")
-	p.parse(&buf, src)
-
-	if strings.Contains(buf.String(), "error") {
-		t.Error(buf.String())
-	}
-}
diff --git a/cmd/golangorg/godoc/spot.go b/cmd/golangorg/godoc/spot.go
deleted file mode 100644
index 95ffa4b..0000000
--- a/cmd/golangorg/godoc/spot.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// 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.
-
-package godoc
-
-// ----------------------------------------------------------------------------
-// SpotInfo
-
-// A SpotInfo value describes a particular identifier spot in a given file;
-// It encodes three values: the SpotKind (declaration or use), a line or
-// snippet index "lori", and whether it's a line or index.
-//
-// The following encoding is used:
-//
-//   bits    32   4    1       0
-//   value    [lori|kind|isIndex]
-//
-type SpotInfo uint32
-
-// SpotKind describes whether an identifier is declared (and what kind of
-// declaration) or used.
-type SpotKind uint32
-
-const (
-	PackageClause SpotKind = iota
-	ImportDecl
-	ConstDecl
-	TypeDecl
-	VarDecl
-	FuncDecl
-	MethodDecl
-	Use
-	nKinds
-)
-
-var (
-	// These must match the SpotKind values above.
-	name = []string{
-		"Packages",
-		"Imports",
-		"Constants",
-		"Types",
-		"Variables",
-		"Functions",
-		"Methods",
-		"Uses",
-		"Unknown",
-	}
-)
-
-func (x SpotKind) Name() string { return name[x] }
-
-func init() {
-	// sanity check: if nKinds is too large, the SpotInfo
-	// accessor functions may need to be updated
-	if nKinds > 8 {
-		panic("internal error: nKinds > 8")
-	}
-}
-
-// makeSpotInfo makes a SpotInfo.
-func makeSpotInfo(kind SpotKind, lori int, isIndex bool) SpotInfo {
-	// encode lori: bits [4..32)
-	x := SpotInfo(lori) << 4
-	if int(x>>4) != lori {
-		// lori value doesn't fit - since snippet indices are
-		// most certainly always smaller then 1<<28, this can
-		// only happen for line numbers; give it no line number (= 0)
-		x = 0
-	}
-	// encode kind: bits [1..4)
-	x |= SpotInfo(kind) << 1
-	// encode isIndex: bit 0
-	if isIndex {
-		x |= 1
-	}
-	return x
-}
-
-func (x SpotInfo) Kind() SpotKind { return SpotKind(x >> 1 & 7) }
-func (x SpotInfo) Lori() int      { return int(x >> 4) }
-func (x SpotInfo) IsIndex() bool  { return x&1 != 0 }
diff --git a/cmd/golangorg/godoc/tab.go b/cmd/golangorg/godoc/tab.go
deleted file mode 100644
index d314ac7..0000000
--- a/cmd/golangorg/godoc/tab.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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.
-
-// TODO(bradfitz,adg): move to util
-
-package godoc
-
-import "io"
-
-var spaces = []byte("                                ") // 32 spaces seems like a good number
-
-const (
-	indenting = iota
-	collecting
-)
-
-// A tconv is an io.Writer filter for converting leading tabs into spaces.
-type tconv struct {
-	output io.Writer
-	state  int // indenting or collecting
-	indent int // valid if state == indenting
-	p      *Presentation
-}
-
-func (p *tconv) writeIndent() (err error) {
-	i := p.indent
-	for i >= len(spaces) {
-		i -= len(spaces)
-		if _, err = p.output.Write(spaces); err != nil {
-			return
-		}
-	}
-	// i < len(spaces)
-	if i > 0 {
-		_, err = p.output.Write(spaces[0:i])
-	}
-	return
-}
-
-func (p *tconv) Write(data []byte) (n int, err error) {
-	if len(data) == 0 {
-		return
-	}
-	pos := 0 // valid if p.state == collecting
-	var b byte
-	for n, b = range data {
-		switch p.state {
-		case indenting:
-			switch b {
-			case '\t':
-				p.indent += p.p.TabWidth
-			case '\n':
-				p.indent = 0
-				if _, err = p.output.Write(data[n : n+1]); err != nil {
-					return
-				}
-			case ' ':
-				p.indent++
-			default:
-				p.state = collecting
-				pos = n
-				if err = p.writeIndent(); err != nil {
-					return
-				}
-			}
-		case collecting:
-			if b == '\n' {
-				p.state = indenting
-				p.indent = 0
-				if _, err = p.output.Write(data[pos : n+1]); err != nil {
-					return
-				}
-			}
-		}
-	}
-	n = len(data)
-	if pos < n && p.state == collecting {
-		_, err = p.output.Write(data[pos:])
-	}
-	return
-}
diff --git a/cmd/golangorg/godoc/template.go b/cmd/golangorg/godoc/template.go
deleted file mode 100644
index d236f5d..0000000
--- a/cmd/golangorg/godoc/template.go
+++ /dev/null
@@ -1,179 +0,0 @@
-// Copyright 2011 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.
-
-// Template support for writing HTML documents.
-// Documents that include Template: true in their
-// metadata are executed as input to text/template.
-//
-// This file defines functions for those templates to invoke.
-
-// The template uses the function "code" to inject program
-// source into the output by extracting code from files and
-// injecting them as HTML-escaped <pre> blocks.
-//
-// The syntax is simple: 1, 2, or 3 space-separated arguments:
-//
-// Whole file:
-//	{{code "foo.go"}}
-// One line (here the signature of main):
-//	{{code "foo.go" `/^func.main/`}}
-// Block of text, determined by start and end (here the body of main):
-//	{{code "foo.go" `/^func.main/` `/^}/`
-//
-// Patterns can be `/regular expression/`, a decimal number, or "$"
-// to signify the end of the file. In multi-line matches,
-// lines that end with the four characters
-//	OMIT
-// are omitted from the output, making it easy to provide marker
-// lines in the input that will not appear in the output but are easy
-// to identify by pattern.
-
-package godoc
-
-import (
-	"bytes"
-	"fmt"
-	"log"
-	"regexp"
-	"strings"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// Functions in this file panic on error, but the panic is recovered
-// to an error by 'code'.
-
-// contents reads and returns the content of the named file
-// (from the virtual file system, so for example /doc refers to $GOROOT/doc).
-func (c *Corpus) contents(name string) string {
-	file, err := vfs.ReadFile(c.fs, name)
-	if err != nil {
-		log.Panic(err)
-	}
-	return string(file)
-}
-
-// stringFor returns a textual representation of the arg, formatted according to its nature.
-func stringFor(arg interface{}) string {
-	switch arg := arg.(type) {
-	case int:
-		return fmt.Sprintf("%d", arg)
-	case string:
-		if len(arg) > 2 && arg[0] == '/' && arg[len(arg)-1] == '/' {
-			return fmt.Sprintf("%#q", arg)
-		}
-		return fmt.Sprintf("%q", arg)
-	default:
-		log.Panicf("unrecognized argument: %v type %T", arg, arg)
-	}
-	return ""
-}
-
-func (p *Presentation) code(file string, arg ...interface{}) (s string, err error) {
-	defer func() {
-		if r := recover(); r != nil {
-			err = fmt.Errorf("%v", r)
-		}
-	}()
-
-	text := p.Corpus.contents(file)
-	var command string
-	switch len(arg) {
-	case 0:
-		// text is already whole file.
-		command = fmt.Sprintf("code %q", file)
-	case 1:
-		command = fmt.Sprintf("code %q %s", file, stringFor(arg[0]))
-		text = p.Corpus.oneLine(file, text, arg[0])
-	case 2:
-		command = fmt.Sprintf("code %q %s %s", file, stringFor(arg[0]), stringFor(arg[1]))
-		text = p.Corpus.multipleLines(file, text, arg[0], arg[1])
-	default:
-		return "", fmt.Errorf("incorrect code invocation: code %q [%v, ...] (%d arguments)", file, arg[0], len(arg))
-	}
-	// Trim spaces from output.
-	text = strings.Trim(text, "\n")
-	// Replace tabs by spaces, which work better in HTML.
-	text = strings.Replace(text, "\t", "    ", -1)
-	var buf bytes.Buffer
-	// HTML-escape text and syntax-color comments like elsewhere.
-	FormatText(&buf, []byte(text), -1, true, "", nil)
-	// Include the command as a comment.
-	text = fmt.Sprintf("<pre><!--{{%s}}\n-->%s</pre>", command, buf.Bytes())
-	return text, nil
-}
-
-// parseArg returns the integer or string value of the argument and tells which it is.
-func parseArg(arg interface{}, file string, max int) (ival int, sval string, isInt bool) {
-	switch n := arg.(type) {
-	case int:
-		if n <= 0 || n > max {
-			log.Panicf("%q:%d is out of range", file, n)
-		}
-		return n, "", true
-	case string:
-		return 0, n, false
-	}
-	log.Panicf("unrecognized argument %v type %T", arg, arg)
-	return
-}
-
-// oneLine returns the single line generated by a two-argument code invocation.
-func (c *Corpus) oneLine(file, text string, arg interface{}) string {
-	lines := strings.SplitAfter(c.contents(file), "\n")
-	line, pattern, isInt := parseArg(arg, file, len(lines))
-	if isInt {
-		return lines[line-1]
-	}
-	return lines[match(file, 0, lines, pattern)-1]
-}
-
-// multipleLines returns the text generated by a three-argument code invocation.
-func (c *Corpus) multipleLines(file, text string, arg1, arg2 interface{}) string {
-	lines := strings.SplitAfter(c.contents(file), "\n")
-	line1, pattern1, isInt1 := parseArg(arg1, file, len(lines))
-	line2, pattern2, isInt2 := parseArg(arg2, file, len(lines))
-	if !isInt1 {
-		line1 = match(file, 0, lines, pattern1)
-	}
-	if !isInt2 {
-		line2 = match(file, line1, lines, pattern2)
-	} else if line2 < line1 {
-		log.Panicf("lines out of order for %q: %d %d", text, line1, line2)
-	}
-	for k := line1 - 1; k < line2; k++ {
-		if strings.HasSuffix(lines[k], "OMIT\n") {
-			lines[k] = ""
-		}
-	}
-	return strings.Join(lines[line1-1:line2], "")
-}
-
-// match identifies the input line that matches the pattern in a code invocation.
-// If start>0, match lines starting there rather than at the beginning.
-// The return value is 1-indexed.
-func match(file string, start int, lines []string, pattern string) int {
-	// $ matches the end of the file.
-	if pattern == "$" {
-		if len(lines) == 0 {
-			log.Panicf("%q: empty file", file)
-		}
-		return len(lines)
-	}
-	// /regexp/ matches the line that matches the regexp.
-	if len(pattern) > 2 && pattern[0] == '/' && pattern[len(pattern)-1] == '/' {
-		re, err := regexp.Compile(pattern[1 : len(pattern)-1])
-		if err != nil {
-			log.Panic(err)
-		}
-		for i := start; i < len(lines); i++ {
-			if re.MatchString(lines[i]) {
-				return i + 1
-			}
-		}
-		log.Panicf("%s: no match for %#q", file, pattern)
-	}
-	log.Panicf("unrecognized pattern: %q", pattern)
-	return 0
-}
diff --git a/cmd/golangorg/godoc/util/throttle.go b/cmd/golangorg/godoc/util/throttle.go
deleted file mode 100644
index 53d9ba6..0000000
--- a/cmd/golangorg/godoc/util/throttle.go
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2011 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.
-
-package util
-
-import "time"
-
-// A Throttle permits throttling of a goroutine by
-// calling the Throttle method repeatedly.
-//
-type Throttle struct {
-	f  float64       // f = (1-r)/r for 0 < r < 1
-	dt time.Duration // minimum run time slice; >= 0
-	tr time.Duration // accumulated time running
-	ts time.Duration // accumulated time stopped
-	tt time.Time     // earliest throttle time (= time Throttle returned + tm)
-}
-
-// NewThrottle creates a new Throttle with a throttle value r and
-// a minimum allocated run time slice of dt:
-//
-//	r == 0: "empty" throttle; the goroutine is always sleeping
-//	r == 1: full throttle; the goroutine is never sleeping
-//
-// A value of r == 0.6 throttles a goroutine such that it runs
-// approx. 60% of the time, and sleeps approx. 40% of the time.
-// Values of r < 0 or r > 1 are clamped down to values between 0 and 1.
-// Values of dt < 0 are set to 0.
-//
-func NewThrottle(r float64, dt time.Duration) *Throttle {
-	var f float64
-	switch {
-	case r <= 0:
-		f = -1 // indicates always sleep
-	case r >= 1:
-		f = 0 // assume r == 1 (never sleep)
-	default:
-		// 0 < r < 1
-		f = (1 - r) / r
-	}
-	if dt < 0 {
-		dt = 0
-	}
-	return &Throttle{f: f, dt: dt, tt: time.Now().Add(dt)}
-}
-
-// Throttle calls time.Sleep such that over time the ratio tr/ts between
-// accumulated run (tr) and sleep times (ts) approximates the value 1/(1-r)
-// where r is the throttle value. Throttle returns immediately (w/o sleeping)
-// if less than tm ns have passed since the last call to Throttle.
-//
-func (p *Throttle) Throttle() {
-	if p.f < 0 {
-		select {} // always sleep
-	}
-
-	t0 := time.Now()
-	if t0.Before(p.tt) {
-		return // keep running (minimum time slice not exhausted yet)
-	}
-
-	// accumulate running time
-	p.tr += t0.Sub(p.tt) + p.dt
-
-	// compute sleep time
-	// Over time we want:
-	//
-	//	tr/ts = r/(1-r)
-	//
-	// Thus:
-	//
-	//	ts = tr*f with f = (1-r)/r
-	//
-	// After some incremental run time δr added to the total run time
-	// tr, the incremental sleep-time δs to get to the same ratio again
-	// after waking up from time.Sleep is:
-	if δs := time.Duration(float64(p.tr)*p.f) - p.ts; δs > 0 {
-		time.Sleep(δs)
-	}
-
-	// accumulate (actual) sleep time
-	t1 := time.Now()
-	p.ts += t1.Sub(t0)
-
-	// set earliest next throttle time
-	p.tt = t1.Add(p.dt)
-}
diff --git a/cmd/golangorg/godoc/util/util.go b/cmd/golangorg/godoc/util/util.go
deleted file mode 100644
index f0a26ef..0000000
--- a/cmd/golangorg/godoc/util/util.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// 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.
-
-// Package util contains utility types and functions for godoc.
-package util // import "golang.org/x/website/cmd/golangorg/godoc/util"
-
-import (
-	pathpkg "path"
-	"sync"
-	"time"
-	"unicode/utf8"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// An RWValue wraps a value and permits mutually exclusive
-// access to it and records the time the value was last set.
-type RWValue struct {
-	mutex     sync.RWMutex
-	value     interface{}
-	timestamp time.Time // time of last set()
-}
-
-func (v *RWValue) Set(value interface{}) {
-	v.mutex.Lock()
-	v.value = value
-	v.timestamp = time.Now()
-	v.mutex.Unlock()
-}
-
-func (v *RWValue) Get() (interface{}, time.Time) {
-	v.mutex.RLock()
-	defer v.mutex.RUnlock()
-	return v.value, v.timestamp
-}
-
-// IsText reports whether a significant prefix of s looks like correct UTF-8;
-// that is, if it is likely that s is human-readable text.
-func IsText(s []byte) bool {
-	const max = 1024 // at least utf8.UTFMax
-	if len(s) > max {
-		s = s[0:max]
-	}
-	for i, c := range string(s) {
-		if i+utf8.UTFMax > len(s) {
-			// last char may be incomplete - ignore
-			break
-		}
-		if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' {
-			// decoding error or control character - not a text file
-			return false
-		}
-	}
-	return true
-}
-
-// textExt[x] is true if the extension x indicates a text file, and false otherwise.
-var textExt = map[string]bool{
-	".css": false, // must be served raw
-	".js":  false, // must be served raw
-}
-
-// IsTextFile reports whether the file has a known extension indicating
-// a text file, or if a significant chunk of the specified file looks like
-// correct UTF-8; that is, if it is likely that the file contains human-
-// readable text.
-func IsTextFile(fs vfs.Opener, filename string) bool {
-	// if the extension is known, use it for decision making
-	if isText, found := textExt[pathpkg.Ext(filename)]; found {
-		return isText
-	}
-
-	// the extension is not known; read an initial chunk
-	// of the file and check if it looks like text
-	f, err := fs.Open(filename)
-	if err != nil {
-		return false
-	}
-	defer f.Close()
-
-	var buf [1024]byte
-	n, err := f.Read(buf[0:])
-	if err != nil {
-		return false
-	}
-
-	return IsText(buf[0:n])
-}
diff --git a/cmd/golangorg/godoc/versions.go b/cmd/golangorg/godoc/versions.go
deleted file mode 100644
index f03c714..0000000
--- a/cmd/golangorg/godoc/versions.go
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2018 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 caches information about which standard library types, methods,
-// and functions appeared in what version of Go
-
-package godoc
-
-import (
-	"bufio"
-	"go/build"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
-	"unicode"
-)
-
-// apiVersions is a map of packages to information about those packages'
-// symbols and when they were added to Go.
-//
-// Only things added after Go1 are tracked. Version strings are of the
-// form "1.1", "1.2", etc.
-type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
-
-// pkgAPIVersions contains information about which version of Go added
-// certain package symbols.
-//
-// Only things added after Go1 are tracked. Version strings are of the
-// form "1.1", "1.2", etc.
-type pkgAPIVersions struct {
-	typeSince   map[string]string            // "Server" -> "1.7"
-	methodSince map[string]map[string]string // "*Server" ->"Shutdown"->1.8
-	funcSince   map[string]string            // "NewServer" -> "1.7"
-	fieldSince  map[string]map[string]string // "ClientTrace" -> "Got1xxResponse" -> "1.11"
-}
-
-// sinceVersionFunc returns a string (such as "1.7") specifying which Go
-// version introduced a symbol, unless it was introduced in Go1, in
-// which case it returns the empty string.
-//
-// The kind is one of "type", "method", or "func".
-//
-// The receiver is only used for "methods" and specifies the receiver type,
-// such as "*Server".
-//
-// The name is the symbol name ("Server") and the pkg is the package
-// ("net/http").
-func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
-	pv := v[pkg]
-	switch kind {
-	case "func":
-		return pv.funcSince[name]
-	case "type":
-		return pv.typeSince[name]
-	case "method":
-		return pv.methodSince[receiver][name]
-	}
-	return ""
-}
-
-// versionedRow represents an API feature, a parsed line of a
-// $GOROOT/api/go.*txt file.
-type versionedRow struct {
-	pkg        string // "net/http"
-	kind       string // "type", "func", "method", "field" TODO: "const", "var"
-	recv       string // for methods, the receiver type ("Server", "*Server")
-	name       string // name of type, (struct) field, func, method
-	structName string // for struct fields, the outer struct name
-}
-
-// versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
-type versionParser struct {
-	res apiVersions // initialized lazily
-}
-
-func (vp *versionParser) parseFile(name string) error {
-	base := filepath.Base(name)
-	ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
-	if ver == "1" {
-		return nil
-	}
-	f, err := os.Open(name)
-	if err != nil {
-		return err
-	}
-	defer f.Close()
-
-	sc := bufio.NewScanner(f)
-	for sc.Scan() {
-		row, ok := parseRow(sc.Text())
-		if !ok {
-			continue
-		}
-		if vp.res == nil {
-			vp.res = make(apiVersions)
-		}
-		pkgi, ok := vp.res[row.pkg]
-		if !ok {
-			pkgi = pkgAPIVersions{
-				typeSince:   make(map[string]string),
-				methodSince: make(map[string]map[string]string),
-				funcSince:   make(map[string]string),
-				fieldSince:  make(map[string]map[string]string),
-			}
-			vp.res[row.pkg] = pkgi
-		}
-		switch row.kind {
-		case "func":
-			pkgi.funcSince[row.name] = ver
-		case "type":
-			pkgi.typeSince[row.name] = ver
-		case "method":
-			if _, ok := pkgi.methodSince[row.recv]; !ok {
-				pkgi.methodSince[row.recv] = make(map[string]string)
-			}
-			pkgi.methodSince[row.recv][row.name] = ver
-		case "field":
-			if _, ok := pkgi.fieldSince[row.structName]; !ok {
-				pkgi.fieldSince[row.structName] = make(map[string]string)
-			}
-			pkgi.fieldSince[row.structName][row.name] = ver
-		}
-	}
-	return sc.Err()
-}
-
-func parseRow(s string) (vr versionedRow, ok bool) {
-	if !strings.HasPrefix(s, "pkg ") {
-		// Skip comments, blank lines, etc.
-		return
-	}
-	rest := s[len("pkg "):]
-	endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/' || unicode.IsDigit(r)) })
-	if endPkg == -1 {
-		return
-	}
-	vr.pkg, rest = rest[:endPkg], rest[endPkg:]
-	if !strings.HasPrefix(rest, ", ") {
-		// If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form:
-		//   pkg syscall (darwin-amd64), const ImplementsGetwd = false
-		// We skip those for now.
-		return
-	}
-	rest = rest[len(", "):]
-
-	switch {
-	case strings.HasPrefix(rest, "type "):
-		rest = rest[len("type "):]
-		sp := strings.IndexByte(rest, ' ')
-		if sp == -1 {
-			return
-		}
-		vr.name, rest = rest[:sp], rest[sp+1:]
-		if !strings.HasPrefix(rest, "struct, ") {
-			vr.kind = "type"
-			return vr, true
-		}
-		rest = rest[len("struct, "):]
-		if i := strings.IndexByte(rest, ' '); i != -1 {
-			vr.kind = "field"
-			vr.structName = vr.name
-			vr.name = rest[:i]
-			return vr, true
-		}
-	case strings.HasPrefix(rest, "func "):
-		vr.kind = "func"
-		rest = rest[len("func "):]
-		if i := strings.IndexByte(rest, '('); i != -1 {
-			vr.name = rest[:i]
-			return vr, true
-		}
-	case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
-		vr.kind = "method"
-		rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
-		sp := strings.IndexByte(rest, ' ')
-		if sp == -1 {
-			return
-		}
-		vr.recv = strings.Trim(rest[:sp], "()") // "*File"
-		rest = rest[sp+1:]                      // SetMode(os.FileMode)
-		paren := strings.IndexByte(rest, '(')
-		if paren == -1 {
-			return
-		}
-		vr.name = rest[:paren]
-		return vr, true
-	}
-	return // TODO: handle more cases
-}
-
-// InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
-// which API features were added in which Go releases.
-func (c *Corpus) InitVersionInfo() {
-	var err error
-	c.pkgAPIInfo, err = parsePackageAPIInfo()
-	if err != nil {
-		// TODO: consider making this fatal, after the Go 1.11 cycle.
-		log.Printf("godoc: error parsing API version files: %v", err)
-	}
-}
-
-func parsePackageAPIInfo() (apiVersions, error) {
-	var apiGlob string
-	if os.Getenv("GOROOT") == "" {
-		apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
-	} else {
-		apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
-	}
-
-	files, err := filepath.Glob(apiGlob)
-	if err != nil {
-		return nil, err
-	}
-
-	vp := new(versionParser)
-	for _, f := range files {
-		if err := vp.parseFile(f); err != nil {
-			return nil, err
-		}
-	}
-	return vp.res, nil
-}
diff --git a/cmd/golangorg/godoc/versions_test.go b/cmd/golangorg/godoc/versions_test.go
deleted file mode 100644
index ad2d5e4..0000000
--- a/cmd/golangorg/godoc/versions_test.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2018 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.
-
-package godoc
-
-import (
-	"go/build"
-	"testing"
-)
-
-func TestParseVersionRow(t *testing.T) {
-	tests := []struct {
-		row  string
-		want versionedRow
-	}{
-		{
-			row: "# comment",
-		},
-		{
-			row: "",
-		},
-		{
-			row: "pkg archive/tar, type Writer struct",
-			want: versionedRow{
-				pkg:  "archive/tar",
-				kind: "type",
-				name: "Writer",
-			},
-		},
-		{
-			row: "pkg archive/tar, type Header struct, AccessTime time.Time",
-			want: versionedRow{
-				pkg:        "archive/tar",
-				kind:       "field",
-				structName: "Header",
-				name:       "AccessTime",
-			},
-		},
-		{
-			row: "pkg archive/tar, method (*Reader) Read([]uint8) (int, error)",
-			want: versionedRow{
-				pkg:  "archive/tar",
-				kind: "method",
-				name: "Read",
-				recv: "*Reader",
-			},
-		},
-		{
-			row: "pkg archive/zip, func FileInfoHeader(os.FileInfo) (*FileHeader, error)",
-			want: versionedRow{
-				pkg:  "archive/zip",
-				kind: "func",
-				name: "FileInfoHeader",
-			},
-		},
-		{
-			row: "pkg encoding/base32, method (Encoding) WithPadding(int32) *Encoding",
-			want: versionedRow{
-				pkg:  "encoding/base32",
-				kind: "method",
-				name: "WithPadding",
-				recv: "Encoding",
-			},
-		},
-	}
-
-	for i, tt := range tests {
-		got, ok := parseRow(tt.row)
-		if !ok {
-			got = versionedRow{}
-		}
-		if got != tt.want {
-			t.Errorf("%d. parseRow(%q) = %+v; want %+v", i, tt.row, got, tt.want)
-		}
-	}
-}
-
-// hasTag checks whether a given release tag is contained in the current version
-// of the go binary.
-func hasTag(t string) bool {
-	for _, v := range build.Default.ReleaseTags {
-		if t == v {
-			return true
-		}
-	}
-	return false
-}
-
-func TestAPIVersion(t *testing.T) {
-	av, err := parsePackageAPIInfo()
-	if err != nil {
-		t.Fatal(err)
-	}
-	for _, tc := range []struct {
-		kind     string
-		pkg      string
-		name     string
-		receiver string
-		want     string
-	}{
-		// Things that were added post-1.0 should appear
-		{"func", "archive/tar", "FileInfoHeader", "", "1.1"},
-		{"type", "bufio", "Scanner", "", "1.1"},
-		{"method", "bufio", "WriteTo", "*Reader", "1.1"},
-
-		{"func", "bytes", "LastIndexByte", "", "1.5"},
-		{"type", "crypto", "Decrypter", "", "1.5"},
-		{"method", "crypto/rsa", "Decrypt", "*PrivateKey", "1.5"},
-		{"method", "debug/dwarf", "GoString", "Class", "1.5"},
-
-		{"func", "os", "IsTimeout", "", "1.10"},
-		{"type", "strings", "Builder", "", "1.10"},
-		{"method", "strings", "WriteString", "*Builder", "1.10"},
-
-		// Things from package syscall should never appear
-		{"func", "syscall", "FchFlags", "", ""},
-		{"type", "syscall", "Inet4Pktinfo", "", ""},
-
-		// Things added in Go 1 should never appear
-		{"func", "archive/tar", "NewReader", "", ""},
-		{"type", "archive/tar", "Header", "", ""},
-		{"method", "archive/tar", "Next", "*Reader", ""},
-	} {
-		if tc.want != "" && !hasTag("go"+tc.want) {
-			continue
-		}
-		if got := av.sinceVersionFunc(tc.kind, tc.receiver, tc.name, tc.pkg); got != tc.want {
-			t.Errorf(`sinceFunc("%s", "%s", "%s", "%s") = "%s"; want "%s"`, tc.kind, tc.receiver, tc.name, tc.pkg, got, tc.want)
-		}
-	}
-}
diff --git a/cmd/golangorg/godoc/vfs/emptyvfs.go b/cmd/golangorg/godoc/vfs/emptyvfs.go
deleted file mode 100644
index 0803206..0000000
--- a/cmd/golangorg/godoc/vfs/emptyvfs.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2016 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.
-
-package vfs
-
-import (
-	"fmt"
-	"os"
-	"time"
-)
-
-// NewNameSpace returns a NameSpace pre-initialized with an empty
-// emulated directory mounted on the root mount point "/". This
-// allows directory traversal routines to work properly even if
-// a folder is not explicitly mounted at root by the user.
-func NewNameSpace() NameSpace {
-	ns := NameSpace{}
-	ns.Bind("/", &emptyVFS{}, "/", BindReplace)
-	return ns
-}
-
-// type emptyVFS emulates a FileSystem consisting of an empty directory
-type emptyVFS struct{}
-
-// Open implements Opener. Since emptyVFS is an empty directory, all
-// attempts to open a file should returns errors.
-func (e *emptyVFS) Open(path string) (ReadSeekCloser, error) {
-	if path == "/" {
-		return nil, fmt.Errorf("open: / is a directory")
-	}
-	return nil, os.ErrNotExist
-}
-
-// Stat returns os.FileInfo  for an empty directory if the path is
-// is root "/" or error. os.FileInfo is implemented by emptyVFS
-func (e *emptyVFS) Stat(path string) (os.FileInfo, error) {
-	if path == "/" {
-		return e, nil
-	}
-	return nil, os.ErrNotExist
-}
-
-func (e *emptyVFS) Lstat(path string) (os.FileInfo, error) {
-	return e.Stat(path)
-}
-
-// ReadDir returns an empty os.FileInfo slice for "/", else error.
-func (e *emptyVFS) ReadDir(path string) ([]os.FileInfo, error) {
-	if path == "/" {
-		return []os.FileInfo{}, nil
-	}
-	return nil, os.ErrNotExist
-}
-
-func (e *emptyVFS) String() string {
-	return "emptyVFS(/)"
-}
-
-func (e *emptyVFS) RootType(path string) RootType {
-	return ""
-}
-
-// These functions below implement os.FileInfo for the single
-// empty emulated directory.
-
-func (e *emptyVFS) Name() string {
-	return "/"
-}
-
-func (e *emptyVFS) Size() int64 {
-	return 0
-}
-
-func (e *emptyVFS) Mode() os.FileMode {
-	return os.ModeDir | os.ModePerm
-}
-
-func (e *emptyVFS) ModTime() time.Time {
-	return time.Time{}
-}
-
-func (e *emptyVFS) IsDir() bool {
-	return true
-}
-
-func (e *emptyVFS) Sys() interface{} {
-	return nil
-}
diff --git a/cmd/golangorg/godoc/vfs/emptyvfs_test.go b/cmd/golangorg/godoc/vfs/emptyvfs_test.go
deleted file mode 100644
index 8f5926d..0000000
--- a/cmd/golangorg/godoc/vfs/emptyvfs_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2016 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.
-
-package vfs_test
-
-import (
-	"testing"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/mapfs"
-)
-
-func TestNewNameSpace(t *testing.T) {
-
-	// We will mount this filesystem under /fs1
-	mount := mapfs.New(map[string]string{"fs1file": "abcdefgh"})
-
-	// Existing process. This should give error on Stat("/")
-	t1 := vfs.NameSpace{}
-	t1.Bind("/fs1", mount, "/", vfs.BindReplace)
-
-	// using NewNameSpace. This should work fine.
-	t2 := vfs.NewNameSpace()
-	t2.Bind("/fs1", mount, "/", vfs.BindReplace)
-
-	testcases := map[string][]bool{
-		"/":            {false, true},
-		"/fs1":         {true, true},
-		"/fs1/fs1file": {true, true},
-	}
-
-	fss := []vfs.FileSystem{t1, t2}
-
-	for j, fs := range fss {
-		for k, v := range testcases {
-			_, err := fs.Stat(k)
-			result := err == nil
-			if result != v[j] {
-				t.Errorf("fs: %d, testcase: %s, want: %v, got: %v, err: %s", j, k, v[j], result, err)
-			}
-		}
-	}
-
-	fi, err := t2.Stat("/")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if fi.Name() != "/" {
-		t.Errorf("t2.Name() : want:%s got:%s", "/", fi.Name())
-	}
-
-	if !fi.ModTime().IsZero() {
-		t.Errorf("t2.Modime() : want:%v got:%v", time.Time{}, fi.ModTime())
-	}
-}
diff --git a/cmd/golangorg/godoc/vfs/gatefs/gatefs.go b/cmd/golangorg/godoc/vfs/gatefs/gatefs.go
deleted file mode 100644
index e184857..0000000
--- a/cmd/golangorg/godoc/vfs/gatefs/gatefs.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// 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.
-
-// Package gatefs provides an implementation of the FileSystem
-// interface that wraps another FileSystem and limits its concurrency.
-package gatefs // import "golang.org/x/website/cmd/golangorg/godoc/vfs/gatefs"
-
-import (
-	"fmt"
-	"os"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// New returns a new FileSystem that delegates to fs.
-// If gateCh is non-nil and buffered, it's used as a gate
-// to limit concurrency on calls to fs.
-func New(fs vfs.FileSystem, gateCh chan bool) vfs.FileSystem {
-	if cap(gateCh) == 0 {
-		return fs
-	}
-	return gatefs{fs, gate(gateCh)}
-}
-
-type gate chan bool
-
-func (g gate) enter() { g <- true }
-func (g gate) leave() { <-g }
-
-type gatefs struct {
-	fs vfs.FileSystem
-	gate
-}
-
-func (fs gatefs) String() string {
-	return fmt.Sprintf("gated(%s, %d)", fs.fs.String(), cap(fs.gate))
-}
-
-func (fs gatefs) RootType(path string) vfs.RootType {
-	return fs.fs.RootType(path)
-}
-
-func (fs gatefs) Open(p string) (vfs.ReadSeekCloser, error) {
-	fs.enter()
-	defer fs.leave()
-	rsc, err := fs.fs.Open(p)
-	if err != nil {
-		return nil, err
-	}
-	return gatef{rsc, fs.gate}, nil
-}
-
-func (fs gatefs) Lstat(p string) (os.FileInfo, error) {
-	fs.enter()
-	defer fs.leave()
-	return fs.fs.Lstat(p)
-}
-
-func (fs gatefs) Stat(p string) (os.FileInfo, error) {
-	fs.enter()
-	defer fs.leave()
-	return fs.fs.Stat(p)
-}
-
-func (fs gatefs) ReadDir(p string) ([]os.FileInfo, error) {
-	fs.enter()
-	defer fs.leave()
-	return fs.fs.ReadDir(p)
-}
-
-type gatef struct {
-	rsc vfs.ReadSeekCloser
-	gate
-}
-
-func (f gatef) Read(p []byte) (n int, err error) {
-	f.enter()
-	defer f.leave()
-	return f.rsc.Read(p)
-}
-
-func (f gatef) Seek(offset int64, whence int) (ret int64, err error) {
-	f.enter()
-	defer f.leave()
-	return f.rsc.Seek(offset, whence)
-}
-
-func (f gatef) Close() error {
-	f.enter()
-	defer f.leave()
-	return f.rsc.Close()
-}
diff --git a/cmd/golangorg/godoc/vfs/gatefs/gatefs_test.go b/cmd/golangorg/godoc/vfs/gatefs/gatefs_test.go
deleted file mode 100644
index b21b4a5..0000000
--- a/cmd/golangorg/godoc/vfs/gatefs/gatefs_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2018 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.
-
-package gatefs_test
-
-import (
-	"os"
-	"runtime"
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-	"golang.org/x/website/cmd/golangorg/godoc/vfs/gatefs"
-)
-
-func TestRootType(t *testing.T) {
-	goPath := os.Getenv("GOPATH")
-	var expectedType vfs.RootType
-	if goPath == "" {
-		expectedType = ""
-	} else {
-		expectedType = vfs.RootTypeGoPath
-	}
-	tests := []struct {
-		path   string
-		fsType vfs.RootType
-	}{
-		{runtime.GOROOT(), vfs.RootTypeGoRoot},
-		{goPath, expectedType},
-		{"/tmp/", ""},
-	}
-
-	for _, item := range tests {
-		fs := gatefs.New(vfs.OS(item.path), make(chan bool, 1))
-		if fs.RootType("path") != item.fsType {
-			t.Errorf("unexpected fsType. Expected- %v, Got- %v", item.fsType, fs.RootType("path"))
-		}
-	}
-}
diff --git a/cmd/golangorg/godoc/vfs/httpfs/httpfs.go b/cmd/golangorg/godoc/vfs/httpfs/httpfs.go
deleted file mode 100644
index a46fa23..0000000
--- a/cmd/golangorg/godoc/vfs/httpfs/httpfs.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// 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.
-
-// Package httpfs implements http.FileSystem using a godoc vfs.FileSystem.
-package httpfs // import "golang.org/x/website/cmd/golangorg/godoc/vfs/httpfs"
-
-import (
-	"fmt"
-	"io"
-	"net/http"
-	"os"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-func New(fs vfs.FileSystem) http.FileSystem {
-	return &httpFS{fs}
-}
-
-type httpFS struct {
-	fs vfs.FileSystem
-}
-
-func (h *httpFS) Open(name string) (http.File, error) {
-	fi, err := h.fs.Stat(name)
-	if err != nil {
-		return nil, err
-	}
-	if fi.IsDir() {
-		return &httpDir{h.fs, name, nil}, nil
-	}
-	f, err := h.fs.Open(name)
-	if err != nil {
-		return nil, err
-	}
-	return &httpFile{h.fs, f, name}, nil
-}
-
-// httpDir implements http.File for a directory in a FileSystem.
-type httpDir struct {
-	fs      vfs.FileSystem
-	name    string
-	pending []os.FileInfo
-}
-
-func (h *httpDir) Close() error               { return nil }
-func (h *httpDir) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
-func (h *httpDir) Read([]byte) (int, error) {
-	return 0, fmt.Errorf("cannot Read from directory %s", h.name)
-}
-
-func (h *httpDir) Seek(offset int64, whence int) (int64, error) {
-	if offset == 0 && whence == 0 {
-		h.pending = nil
-		return 0, nil
-	}
-	return 0, fmt.Errorf("unsupported Seek in directory %s", h.name)
-}
-
-func (h *httpDir) Readdir(count int) ([]os.FileInfo, error) {
-	if h.pending == nil {
-		d, err := h.fs.ReadDir(h.name)
-		if err != nil {
-			return nil, err
-		}
-		if d == nil {
-			d = []os.FileInfo{} // not nil
-		}
-		h.pending = d
-	}
-
-	if len(h.pending) == 0 && count > 0 {
-		return nil, io.EOF
-	}
-	if count <= 0 || count > len(h.pending) {
-		count = len(h.pending)
-	}
-	d := h.pending[:count]
-	h.pending = h.pending[count:]
-	return d, nil
-}
-
-// httpFile implements http.File for a file (not directory) in a FileSystem.
-type httpFile struct {
-	fs vfs.FileSystem
-	vfs.ReadSeekCloser
-	name string
-}
-
-func (h *httpFile) Stat() (os.FileInfo, error) { return h.fs.Stat(h.name) }
-func (h *httpFile) Readdir(int) ([]os.FileInfo, error) {
-	return nil, fmt.Errorf("cannot Readdir from file %s", h.name)
-}
diff --git a/cmd/golangorg/godoc/vfs/mapfs/mapfs.go b/cmd/golangorg/godoc/vfs/mapfs/mapfs.go
deleted file mode 100644
index e92efe0..0000000
--- a/cmd/golangorg/godoc/vfs/mapfs/mapfs.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// 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.
-
-// Package mapfs file provides an implementation of the FileSystem
-// interface based on the contents of a map[string]string.
-package mapfs // import "golang.org/x/website/cmd/golangorg/godoc/vfs/mapfs"
-
-import (
-	"io"
-	"os"
-	pathpkg "path"
-	"sort"
-	"strings"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// New returns a new FileSystem from the provided map.
-// Map keys should be forward slash-separated pathnames
-// and not contain a leading slash.
-func New(m map[string]string) vfs.FileSystem {
-	return mapFS(m)
-}
-
-// mapFS is the map based implementation of FileSystem
-type mapFS map[string]string
-
-func (fs mapFS) String() string { return "mapfs" }
-
-func (fs mapFS) RootType(p string) vfs.RootType {
-	return ""
-}
-
-func (fs mapFS) Close() error { return nil }
-
-func filename(p string) string {
-	return strings.TrimPrefix(p, "/")
-}
-
-func (fs mapFS) Open(p string) (vfs.ReadSeekCloser, error) {
-	b, ok := fs[filename(p)]
-	if !ok {
-		return nil, os.ErrNotExist
-	}
-	return nopCloser{strings.NewReader(b)}, nil
-}
-
-func fileInfo(name, contents string) os.FileInfo {
-	return mapFI{name: pathpkg.Base(name), size: len(contents)}
-}
-
-func dirInfo(name string) os.FileInfo {
-	return mapFI{name: pathpkg.Base(name), dir: true}
-}
-
-func (fs mapFS) Lstat(p string) (os.FileInfo, error) {
-	b, ok := fs[filename(p)]
-	if ok {
-		return fileInfo(p, b), nil
-	}
-	ents, _ := fs.ReadDir(p)
-	if len(ents) > 0 {
-		return dirInfo(p), nil
-	}
-	return nil, os.ErrNotExist
-}
-
-func (fs mapFS) Stat(p string) (os.FileInfo, error) {
-	return fs.Lstat(p)
-}
-
-// slashdir returns path.Dir(p), but special-cases paths not beginning
-// with a slash to be in the root.
-func slashdir(p string) string {
-	d := pathpkg.Dir(p)
-	if d == "." {
-		return "/"
-	}
-	if strings.HasPrefix(p, "/") {
-		return d
-	}
-	return "/" + d
-}
-
-func (fs mapFS) ReadDir(p string) ([]os.FileInfo, error) {
-	p = pathpkg.Clean(p)
-	var ents []string
-	fim := make(map[string]os.FileInfo) // base -> fi
-	for fn, b := range fs {
-		dir := slashdir(fn)
-		isFile := true
-		var lastBase string
-		for {
-			if dir == p {
-				base := lastBase
-				if isFile {
-					base = pathpkg.Base(fn)
-				}
-				if fim[base] == nil {
-					var fi os.FileInfo
-					if isFile {
-						fi = fileInfo(fn, b)
-					} else {
-						fi = dirInfo(base)
-					}
-					ents = append(ents, base)
-					fim[base] = fi
-				}
-			}
-			if dir == "/" {
-				break
-			} else {
-				isFile = false
-				lastBase = pathpkg.Base(dir)
-				dir = pathpkg.Dir(dir)
-			}
-		}
-	}
-	if len(ents) == 0 {
-		return nil, os.ErrNotExist
-	}
-
-	sort.Strings(ents)
-	var list []os.FileInfo
-	for _, dir := range ents {
-		list = append(list, fim[dir])
-	}
-	return list, nil
-}
-
-// mapFI is the map-based implementation of FileInfo.
-type mapFI struct {
-	name string
-	size int
-	dir  bool
-}
-
-func (fi mapFI) IsDir() bool        { return fi.dir }
-func (fi mapFI) ModTime() time.Time { return time.Time{} }
-func (fi mapFI) Mode() os.FileMode {
-	if fi.IsDir() {
-		return 0755 | os.ModeDir
-	}
-	return 0444
-}
-func (fi mapFI) Name() string     { return pathpkg.Base(fi.name) }
-func (fi mapFI) Size() int64      { return int64(fi.size) }
-func (fi mapFI) Sys() interface{} { return nil }
-
-type nopCloser struct {
-	io.ReadSeeker
-}
-
-func (nc nopCloser) Close() error { return nil }
diff --git a/cmd/golangorg/godoc/vfs/mapfs/mapfs_test.go b/cmd/golangorg/godoc/vfs/mapfs/mapfs_test.go
deleted file mode 100644
index 6b7db29..0000000
--- a/cmd/golangorg/godoc/vfs/mapfs/mapfs_test.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// 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.
-
-package mapfs
-
-import (
-	"io/ioutil"
-	"os"
-	"reflect"
-	"testing"
-)
-
-func TestOpenRoot(t *testing.T) {
-	fs := New(map[string]string{
-		"foo/bar/three.txt": "a",
-		"foo/bar.txt":       "b",
-		"top.txt":           "c",
-		"other-top.txt":     "d",
-	})
-	tests := []struct {
-		path string
-		want string
-	}{
-		{"/foo/bar/three.txt", "a"},
-		{"foo/bar/three.txt", "a"},
-		{"foo/bar.txt", "b"},
-		{"top.txt", "c"},
-		{"/top.txt", "c"},
-		{"other-top.txt", "d"},
-		{"/other-top.txt", "d"},
-	}
-	for _, tt := range tests {
-		rsc, err := fs.Open(tt.path)
-		if err != nil {
-			t.Errorf("Open(%q) = %v", tt.path, err)
-			continue
-		}
-		slurp, err := ioutil.ReadAll(rsc)
-		if err != nil {
-			t.Error(err)
-		}
-		if string(slurp) != tt.want {
-			t.Errorf("Read(%q) = %q; want %q", tt.path, tt.want, slurp)
-		}
-		rsc.Close()
-	}
-
-	_, err := fs.Open("/xxxx")
-	if !os.IsNotExist(err) {
-		t.Errorf("ReadDir /xxxx = %v; want os.IsNotExist error", err)
-	}
-}
-
-func TestReaddir(t *testing.T) {
-	fs := New(map[string]string{
-		"foo/bar/three.txt": "333",
-		"foo/bar.txt":       "22",
-		"top.txt":           "top.txt file",
-		"other-top.txt":     "other-top.txt file",
-	})
-	tests := []struct {
-		dir  string
-		want []os.FileInfo
-	}{
-		{
-			dir: "/",
-			want: []os.FileInfo{
-				mapFI{name: "foo", dir: true},
-				mapFI{name: "other-top.txt", size: len("other-top.txt file")},
-				mapFI{name: "top.txt", size: len("top.txt file")},
-			},
-		},
-		{
-			dir: "/foo",
-			want: []os.FileInfo{
-				mapFI{name: "bar", dir: true},
-				mapFI{name: "bar.txt", size: 2},
-			},
-		},
-		{
-			dir: "/foo/",
-			want: []os.FileInfo{
-				mapFI{name: "bar", dir: true},
-				mapFI{name: "bar.txt", size: 2},
-			},
-		},
-		{
-			dir: "/foo/bar",
-			want: []os.FileInfo{
-				mapFI{name: "three.txt", size: 3},
-			},
-		},
-	}
-	for _, tt := range tests {
-		fis, err := fs.ReadDir(tt.dir)
-		if err != nil {
-			t.Errorf("ReadDir(%q) = %v", tt.dir, err)
-			continue
-		}
-		if !reflect.DeepEqual(fis, tt.want) {
-			t.Errorf("ReadDir(%q) = %#v; want %#v", tt.dir, fis, tt.want)
-			continue
-		}
-	}
-
-	_, err := fs.ReadDir("/xxxx")
-	if !os.IsNotExist(err) {
-		t.Errorf("ReadDir /xxxx = %v; want os.IsNotExist error", err)
-	}
-}
diff --git a/cmd/golangorg/godoc/vfs/namespace.go b/cmd/golangorg/godoc/vfs/namespace.go
deleted file mode 100644
index b8a1122..0000000
--- a/cmd/golangorg/godoc/vfs/namespace.go
+++ /dev/null
@@ -1,403 +0,0 @@
-// Copyright 2011 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.
-
-package vfs
-
-import (
-	"fmt"
-	"io"
-	"os"
-	pathpkg "path"
-	"sort"
-	"strings"
-	"time"
-)
-
-// Setting debugNS = true will enable debugging prints about
-// name space translations.
-const debugNS = false
-
-// A NameSpace is a file system made up of other file systems
-// mounted at specific locations in the name space.
-//
-// The representation is a map from mount point locations
-// to the list of file systems mounted at that location.  A traditional
-// Unix mount table would use a single file system per mount point,
-// but we want to be able to mount multiple file systems on a single
-// mount point and have the system behave as if the union of those
-// file systems were present at the mount point.
-// For example, if the OS file system has a Go installation in
-// c:\Go and additional Go path trees in  d:\Work1 and d:\Work2, then
-// this name space creates the view we want for the godoc server:
-//
-//	NameSpace{
-//		"/": {
-//			{old: "/", fs: OS(`c:\Go`), new: "/"},
-//		},
-//		"/src/pkg": {
-//			{old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
-//			{old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
-//			{old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
-//		},
-//	}
-//
-// This is created by executing:
-//
-//	ns := NameSpace{}
-//	ns.Bind("/", OS(`c:\Go`), "/", BindReplace)
-//	ns.Bind("/src/pkg", OS(`d:\Work1`), "/src", BindAfter)
-//	ns.Bind("/src/pkg", OS(`d:\Work2`), "/src", BindAfter)
-//
-// A particular mount point entry is a triple (old, fs, new), meaning that to
-// operate on a path beginning with old, replace that prefix (old) with new
-// and then pass that path to the FileSystem implementation fs.
-//
-// If you do not explicitly mount a FileSystem at the root mountpoint "/" of the
-// NameSpace like above, Stat("/") will return a "not found" error which could
-// break typical directory traversal routines. In such cases, use NewNameSpace()
-// to get a NameSpace pre-initialized with an emulated empty directory at root.
-//
-// Given this name space, a ReadDir of /src/pkg/code will check each prefix
-// of the path for a mount point (first /src/pkg/code, then /src/pkg, then /src,
-// then /), stopping when it finds one.  For the above example, /src/pkg/code
-// will find the mount point at /src/pkg:
-//
-//	{old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
-//	{old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
-//	{old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
-//
-// ReadDir will when execute these three calls and merge the results:
-//
-//	OS(`c:\Go`).ReadDir("/src/pkg/code")
-//	OS(`d:\Work1').ReadDir("/src/code")
-//	OS(`d:\Work2').ReadDir("/src/code")
-//
-// Note that the "/src/pkg" in "/src/pkg/code" has been replaced by
-// just "/src" in the final two calls.
-//
-// OS is itself an implementation of a file system: it implements
-// OS(`c:\Go`).ReadDir("/src/pkg/code") as ioutil.ReadDir(`c:\Go\src\pkg\code`).
-//
-// Because the new path is evaluated by fs (here OS(root)), another way
-// to read the mount table is to mentally combine fs+new, so that this table:
-//
-//	{old: "/src/pkg", fs: OS(`c:\Go`), new: "/src/pkg"},
-//	{old: "/src/pkg", fs: OS(`d:\Work1`), new: "/src"},
-//	{old: "/src/pkg", fs: OS(`d:\Work2`), new: "/src"},
-//
-// reads as:
-//
-//	"/src/pkg" -> c:\Go\src\pkg
-//	"/src/pkg" -> d:\Work1\src
-//	"/src/pkg" -> d:\Work2\src
-//
-// An invariant (a redundancy) of the name space representation is that
-// ns[mtpt][i].old is always equal to mtpt (in the example, ns["/src/pkg"]'s
-// mount table entries always have old == "/src/pkg").  The 'old' field is
-// useful to callers, because they receive just a []mountedFS and not any
-// other indication of which mount point was found.
-//
-type NameSpace map[string][]mountedFS
-
-// A mountedFS handles requests for path by replacing
-// a prefix 'old' with 'new' and then calling the fs methods.
-type mountedFS struct {
-	old string
-	fs  FileSystem
-	new string
-}
-
-// hasPathPrefix returns true if x == y or x == y + "/" + more
-func hasPathPrefix(x, y string) bool {
-	return x == y || strings.HasPrefix(x, y) && (strings.HasSuffix(y, "/") || strings.HasPrefix(x[len(y):], "/"))
-}
-
-// translate translates path for use in m, replacing old with new.
-//
-// mountedFS{"/src/pkg", fs, "/src"}.translate("/src/pkg/code") == "/src/code".
-func (m mountedFS) translate(path string) string {
-	path = pathpkg.Clean("/" + path)
-	if !hasPathPrefix(path, m.old) {
-		panic("translate " + path + " but old=" + m.old)
-	}
-	return pathpkg.Join(m.new, path[len(m.old):])
-}
-
-func (NameSpace) String() string {
-	return "ns"
-}
-
-// Fprint writes a text representation of the name space to w.
-func (ns NameSpace) Fprint(w io.Writer) {
-	fmt.Fprint(w, "name space {\n")
-	var all []string
-	for mtpt := range ns {
-		all = append(all, mtpt)
-	}
-	sort.Strings(all)
-	for _, mtpt := range all {
-		fmt.Fprintf(w, "\t%s:\n", mtpt)
-		for _, m := range ns[mtpt] {
-			fmt.Fprintf(w, "\t\t%s %s\n", m.fs, m.new)
-		}
-	}
-	fmt.Fprint(w, "}\n")
-}
-
-// clean returns a cleaned, rooted path for evaluation.
-// It canonicalizes the path so that we can use string operations
-// to analyze it.
-func (NameSpace) clean(path string) string {
-	return pathpkg.Clean("/" + path)
-}
-
-type BindMode int
-
-const (
-	BindReplace BindMode = iota
-	BindBefore
-	BindAfter
-)
-
-// Bind causes references to old to redirect to the path new in newfs.
-// If mode is BindReplace, old redirections are discarded.
-// If mode is BindBefore, this redirection takes priority over existing ones,
-// but earlier ones are still consulted for paths that do not exist in newfs.
-// If mode is BindAfter, this redirection happens only after existing ones
-// have been tried and failed.
-func (ns NameSpace) Bind(old string, newfs FileSystem, new string, mode BindMode) {
-	old = ns.clean(old)
-	new = ns.clean(new)
-	m := mountedFS{old, newfs, new}
-	var mtpt []mountedFS
-	switch mode {
-	case BindReplace:
-		mtpt = append(mtpt, m)
-	case BindAfter:
-		mtpt = append(mtpt, ns.resolve(old)...)
-		mtpt = append(mtpt, m)
-	case BindBefore:
-		mtpt = append(mtpt, m)
-		mtpt = append(mtpt, ns.resolve(old)...)
-	}
-
-	// Extend m.old, m.new in inherited mount point entries.
-	for i := range mtpt {
-		m := &mtpt[i]
-		if m.old != old {
-			if !hasPathPrefix(old, m.old) {
-				// This should not happen.  If it does, panic so
-				// that we can see the call trace that led to it.
-				panic(fmt.Sprintf("invalid Bind: old=%q m={%q, %s, %q}", old, m.old, m.fs.String(), m.new))
-			}
-			suffix := old[len(m.old):]
-			m.old = pathpkg.Join(m.old, suffix)
-			m.new = pathpkg.Join(m.new, suffix)
-		}
-	}
-
-	ns[old] = mtpt
-}
-
-// resolve resolves a path to the list of mountedFS to use for path.
-func (ns NameSpace) resolve(path string) []mountedFS {
-	path = ns.clean(path)
-	for {
-		if m := ns[path]; m != nil {
-			if debugNS {
-				fmt.Printf("resolve %s: %v\n", path, m)
-			}
-			return m
-		}
-		if path == "/" {
-			break
-		}
-		path = pathpkg.Dir(path)
-	}
-	return nil
-}
-
-// Open implements the FileSystem Open method.
-func (ns NameSpace) Open(path string) (ReadSeekCloser, error) {
-	var err error
-	for _, m := range ns.resolve(path) {
-		if debugNS {
-			fmt.Printf("tx %s: %v\n", path, m.translate(path))
-		}
-		tp := m.translate(path)
-		r, err1 := m.fs.Open(tp)
-		if err1 == nil {
-			return r, nil
-		}
-		// IsNotExist errors in overlay FSes can mask real errors in
-		// the underlying FS, so ignore them if there is another error.
-		if err == nil || os.IsNotExist(err) {
-			err = err1
-		}
-	}
-	if err == nil {
-		err = &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
-	}
-	return nil, err
-}
-
-// stat implements the FileSystem Stat and Lstat methods.
-func (ns NameSpace) stat(path string, f func(FileSystem, string) (os.FileInfo, error)) (os.FileInfo, error) {
-	var err error
-	for _, m := range ns.resolve(path) {
-		fi, err1 := f(m.fs, m.translate(path))
-		if err1 == nil {
-			return fi, nil
-		}
-		if err == nil {
-			err = err1
-		}
-	}
-	if err == nil {
-		err = &os.PathError{Op: "stat", Path: path, Err: os.ErrNotExist}
-	}
-	return nil, err
-}
-
-func (ns NameSpace) Stat(path string) (os.FileInfo, error) {
-	return ns.stat(path, FileSystem.Stat)
-}
-
-func (ns NameSpace) Lstat(path string) (os.FileInfo, error) {
-	return ns.stat(path, FileSystem.Lstat)
-}
-
-// dirInfo is a trivial implementation of os.FileInfo for a directory.
-type dirInfo string
-
-func (d dirInfo) Name() string       { return string(d) }
-func (d dirInfo) Size() int64        { return 0 }
-func (d dirInfo) Mode() os.FileMode  { return os.ModeDir | 0555 }
-func (d dirInfo) ModTime() time.Time { return startTime }
-func (d dirInfo) IsDir() bool        { return true }
-func (d dirInfo) Sys() interface{}   { return nil }
-
-var startTime = time.Now()
-
-// ReadDir implements the FileSystem ReadDir method.  It's where most of the magic is.
-// (The rest is in resolve.)
-//
-// Logically, ReadDir must return the union of all the directories that are named
-// by path.  In order to avoid misinterpreting Go packages, of all the directories
-// that contain Go source code, we only include the files from the first,
-// but we include subdirectories from all.
-//
-// ReadDir must also return directory entries needed to reach mount points.
-// If the name space looks like the example in the type NameSpace comment,
-// but c:\Go does not have a src/pkg subdirectory, we still want to be able
-// to find that subdirectory, because we've mounted d:\Work1 and d:\Work2
-// there.  So if we don't see "src" in the directory listing for c:\Go, we add an
-// entry for it before returning.
-//
-func (ns NameSpace) ReadDir(path string) ([]os.FileInfo, error) {
-	path = ns.clean(path)
-
-	var (
-		haveGo   = false
-		haveName = map[string]bool{}
-		all      []os.FileInfo
-		err      error
-		first    []os.FileInfo
-	)
-
-	for _, m := range ns.resolve(path) {
-		dir, err1 := m.fs.ReadDir(m.translate(path))
-		if err1 != nil {
-			if err == nil {
-				err = err1
-			}
-			continue
-		}
-
-		if dir == nil {
-			dir = []os.FileInfo{}
-		}
-
-		if first == nil {
-			first = dir
-		}
-
-		// If we don't yet have Go files in 'all' and this directory
-		// has some, add all the files from this directory.
-		// Otherwise, only add subdirectories.
-		useFiles := false
-		if !haveGo {
-			for _, d := range dir {
-				if strings.HasSuffix(d.Name(), ".go") {
-					useFiles = true
-					haveGo = true
-					break
-				}
-			}
-		}
-
-		for _, d := range dir {
-			name := d.Name()
-			if (d.IsDir() || useFiles) && !haveName[name] {
-				haveName[name] = true
-				all = append(all, d)
-			}
-		}
-	}
-
-	// We didn't find any directories containing Go files.
-	// If some directory returned successfully, use that.
-	if !haveGo {
-		for _, d := range first {
-			if !haveName[d.Name()] {
-				haveName[d.Name()] = true
-				all = append(all, d)
-			}
-		}
-	}
-
-	// Built union.  Add any missing directories needed to reach mount points.
-	for old := range ns {
-		if hasPathPrefix(old, path) && old != path {
-			// Find next element after path in old.
-			elem := old[len(path):]
-			elem = strings.TrimPrefix(elem, "/")
-			if i := strings.Index(elem, "/"); i >= 0 {
-				elem = elem[:i]
-			}
-			if !haveName[elem] {
-				haveName[elem] = true
-				all = append(all, dirInfo(elem))
-			}
-		}
-	}
-
-	if len(all) == 0 {
-		return nil, err
-	}
-
-	sort.Sort(byName(all))
-	return all, nil
-}
-
-// RootType returns the RootType for the given path in the namespace.
-func (ns NameSpace) RootType(path string) RootType {
-	// We resolve the given path to a list of mountedFS and then return
-	// the root type for the filesystem which contains the path.
-	for _, m := range ns.resolve(path) {
-		_, err := m.fs.ReadDir(m.translate(path))
-		// Found a match, return the filesystem's root type
-		if err == nil {
-			return m.fs.RootType(path)
-		}
-	}
-	return ""
-}
-
-// byName implements sort.Interface.
-type byName []os.FileInfo
-
-func (f byName) Len() int           { return len(f) }
-func (f byName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
-func (f byName) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
diff --git a/cmd/golangorg/godoc/vfs/os.go b/cmd/golangorg/godoc/vfs/os.go
deleted file mode 100644
index 35d0509..0000000
--- a/cmd/golangorg/godoc/vfs/os.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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.
-
-package vfs
-
-import (
-	"fmt"
-	"go/build"
-	"io/ioutil"
-	"os"
-	pathpkg "path"
-	"path/filepath"
-	"runtime"
-)
-
-// We expose a new variable because otherwise we need to copy the findGOROOT logic again
-// from cmd/godoc which is already copied twice from the standard library.
-
-// GOROOT is the GOROOT path under which the godoc binary is running.
-// It is needed to check whether a filesystem root is under GOROOT or not.
-// This is set from cmd/godoc/main.go.
-var GOROOT = runtime.GOROOT()
-
-// OS returns an implementation of FileSystem reading from the
-// tree rooted at root.  Recording a root is convenient everywhere
-// but necessary on Windows, because the slash-separated path
-// passed to Open has no way to specify a drive letter.  Using a root
-// lets code refer to OS(`c:\`), OS(`d:\`) and so on.
-func OS(root string) FileSystem {
-	var t RootType
-	switch {
-	case root == GOROOT:
-		t = RootTypeGoRoot
-	case isGoPath(root):
-		t = RootTypeGoPath
-	}
-	return osFS{rootPath: root, rootType: t}
-}
-
-type osFS struct {
-	rootPath string
-	rootType RootType
-}
-
-func isGoPath(path string) bool {
-	for _, bp := range filepath.SplitList(build.Default.GOPATH) {
-		for _, gp := range filepath.SplitList(path) {
-			if bp == gp {
-				return true
-			}
-		}
-	}
-	return false
-}
-
-func (root osFS) String() string { return "os(" + root.rootPath + ")" }
-
-// RootType returns the root type for the filesystem.
-//
-// Note that we ignore the path argument because roottype is a property of
-// this filesystem. But for other filesystems, the roottype might need to be
-// dynamically deduced at call time.
-func (root osFS) RootType(path string) RootType {
-	return root.rootType
-}
-
-func (root osFS) resolve(path string) string {
-	// Clean the path so that it cannot possibly begin with ../.
-	// If it did, the result of filepath.Join would be outside the
-	// tree rooted at root.  We probably won't ever see a path
-	// with .. in it, but be safe anyway.
-	path = pathpkg.Clean("/" + path)
-
-	return filepath.Join(root.rootPath, path)
-}
-
-func (root osFS) Open(path string) (ReadSeekCloser, error) {
-	f, err := os.Open(root.resolve(path))
-	if err != nil {
-		return nil, err
-	}
-	fi, err := f.Stat()
-	if err != nil {
-		f.Close()
-		return nil, err
-	}
-	if fi.IsDir() {
-		f.Close()
-		return nil, fmt.Errorf("Open: %s is a directory", path)
-	}
-	return f, nil
-}
-
-func (root osFS) Lstat(path string) (os.FileInfo, error) {
-	return os.Lstat(root.resolve(path))
-}
-
-func (root osFS) Stat(path string) (os.FileInfo, error) {
-	return os.Stat(root.resolve(path))
-}
-
-func (root osFS) ReadDir(path string) ([]os.FileInfo, error) {
-	return ioutil.ReadDir(root.resolve(path)) // is sorted
-}
diff --git a/cmd/golangorg/godoc/vfs/os_test.go b/cmd/golangorg/godoc/vfs/os_test.go
deleted file mode 100644
index dc968f1..0000000
--- a/cmd/golangorg/godoc/vfs/os_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2018 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.
-
-package vfs_test
-
-import (
-	"os"
-	"runtime"
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-func TestRootType(t *testing.T) {
-	goPath := os.Getenv("GOPATH")
-	var expectedType vfs.RootType
-	if goPath == "" {
-		expectedType = ""
-	} else {
-		expectedType = vfs.RootTypeGoPath
-	}
-	tests := []struct {
-		path   string
-		fsType vfs.RootType
-	}{
-		{runtime.GOROOT(), vfs.RootTypeGoRoot},
-		{goPath, expectedType},
-		{"/tmp/", ""},
-	}
-
-	for _, item := range tests {
-		fs := vfs.OS(item.path)
-		if fs.RootType("path") != item.fsType {
-			t.Errorf("unexpected fsType. Expected- %v, Got- %v", item.fsType, fs.RootType("path"))
-		}
-	}
-}
diff --git a/cmd/golangorg/godoc/vfs/vfs.go b/cmd/golangorg/godoc/vfs/vfs.go
deleted file mode 100644
index 5d9d591..0000000
--- a/cmd/golangorg/godoc/vfs/vfs.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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.
-
-// Package vfs defines types for abstract file system access and provides an
-// implementation accessing the file system of the underlying OS.
-package vfs // import "golang.org/x/website/cmd/golangorg/godoc/vfs"
-
-import (
-	"io"
-	"io/ioutil"
-	"os"
-)
-
-// RootType indicates the type of files contained within a directory.
-//
-// It is used to indicate whether a directory is the root
-// of a GOROOT, a GOPATH, or neither.
-// An empty string represents the case when a directory is neither.
-type RootType string
-
-const (
-	RootTypeGoRoot RootType = "GOROOT"
-	RootTypeGoPath RootType = "GOPATH"
-)
-
-// The FileSystem interface specifies the methods godoc is using
-// to access the file system for which it serves documentation.
-type FileSystem interface {
-	Opener
-	Lstat(path string) (os.FileInfo, error)
-	Stat(path string) (os.FileInfo, error)
-	ReadDir(path string) ([]os.FileInfo, error)
-	RootType(path string) RootType
-	String() string
-}
-
-// Opener is a minimal virtual filesystem that can only open regular files.
-type Opener interface {
-	Open(name string) (ReadSeekCloser, error)
-}
-
-// A ReadSeekCloser can Read, Seek, and Close.
-type ReadSeekCloser interface {
-	io.Reader
-	io.Seeker
-	io.Closer
-}
-
-// ReadFile reads the file named by path from fs and returns the contents.
-func ReadFile(fs Opener, path string) ([]byte, error) {
-	rc, err := fs.Open(path)
-	if err != nil {
-		return nil, err
-	}
-	defer rc.Close()
-	return ioutil.ReadAll(rc)
-}
diff --git a/cmd/golangorg/godoc/vfs/zipfs/zipfs.go b/cmd/golangorg/godoc/vfs/zipfs/zipfs.go
deleted file mode 100644
index eace820..0000000
--- a/cmd/golangorg/godoc/vfs/zipfs/zipfs.go
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright 2011 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.
-
-// Package zipfs file provides an implementation of the FileSystem
-// interface based on the contents of a .zip file.
-//
-// Assumptions:
-//
-// - The file paths stored in the zip file must use a slash ('/') as path
-//   separator; and they must be relative (i.e., they must not start with
-//   a '/' - this is usually the case if the file was created w/o special
-//   options).
-// - The zip file system treats the file paths found in the zip internally
-//   like absolute paths w/o a leading '/'; i.e., the paths are considered
-//   relative to the root of the file system.
-// - All path arguments to file system methods must be absolute paths.
-package zipfs // import "golang.org/x/website/cmd/golangorg/godoc/vfs/zipfs"
-
-import (
-	"archive/zip"
-	"fmt"
-	"go/build"
-	"io"
-	"os"
-	"path"
-	"path/filepath"
-	"sort"
-	"strings"
-	"time"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-// zipFI is the zip-file based implementation of FileInfo
-type zipFI struct {
-	name string    // directory-local name
-	file *zip.File // nil for a directory
-}
-
-func (fi zipFI) Name() string {
-	return fi.name
-}
-
-func (fi zipFI) Size() int64 {
-	if f := fi.file; f != nil {
-		return int64(f.UncompressedSize)
-	}
-	return 0 // directory
-}
-
-func (fi zipFI) ModTime() time.Time {
-	if f := fi.file; f != nil {
-		return f.ModTime()
-	}
-	return time.Time{} // directory has no modified time entry
-}
-
-func (fi zipFI) Mode() os.FileMode {
-	if fi.file == nil {
-		// Unix directories typically are executable, hence 555.
-		return os.ModeDir | 0555
-	}
-	return 0444
-}
-
-func (fi zipFI) IsDir() bool {
-	return fi.file == nil
-}
-
-func (fi zipFI) Sys() interface{} {
-	return nil
-}
-
-// zipFS is the zip-file based implementation of FileSystem
-type zipFS struct {
-	*zip.ReadCloser
-	list zipList
-	name string
-}
-
-func (fs *zipFS) String() string {
-	return "zip(" + fs.name + ")"
-}
-
-func (fs *zipFS) RootType(abspath string) vfs.RootType {
-	var t vfs.RootType
-	switch {
-	case exists(path.Join(vfs.GOROOT, abspath)):
-		t = vfs.RootTypeGoRoot
-	case isGoPath(abspath):
-		t = vfs.RootTypeGoPath
-	}
-	return t
-}
-
-func isGoPath(abspath string) bool {
-	for _, p := range filepath.SplitList(build.Default.GOPATH) {
-		if exists(path.Join(p, abspath)) {
-			return true
-		}
-	}
-	return false
-}
-
-func exists(path string) bool {
-	_, err := os.Stat(path)
-	return err == nil
-}
-
-func (fs *zipFS) Close() error {
-	fs.list = nil
-	return fs.ReadCloser.Close()
-}
-
-func zipPath(name string) (string, error) {
-	name = path.Clean(name)
-	if !path.IsAbs(name) {
-		return "", fmt.Errorf("stat: not an absolute path: %s", name)
-	}
-	return name[1:], nil // strip leading '/'
-}
-
-func isRoot(abspath string) bool {
-	return path.Clean(abspath) == "/"
-}
-
-func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
-	if isRoot(abspath) {
-		return 0, zipFI{
-			name: "",
-			file: nil,
-		}, nil
-	}
-	zippath, err := zipPath(abspath)
-	if err != nil {
-		return 0, zipFI{}, err
-	}
-	i, exact := fs.list.lookup(zippath)
-	if i < 0 {
-		// zippath has leading '/' stripped - print it explicitly
-		return -1, zipFI{}, &os.PathError{Path: "/" + zippath, Err: os.ErrNotExist}
-	}
-	_, name := path.Split(zippath)
-	var file *zip.File
-	if exact {
-		file = fs.list[i] // exact match found - must be a file
-	}
-	return i, zipFI{name, file}, nil
-}
-
-func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) {
-	_, fi, err := fs.stat(abspath)
-	if err != nil {
-		return nil, err
-	}
-	if fi.IsDir() {
-		return nil, fmt.Errorf("Open: %s is a directory", abspath)
-	}
-	r, err := fi.file.Open()
-	if err != nil {
-		return nil, err
-	}
-	return &zipSeek{fi.file, r}, nil
-}
-
-type zipSeek struct {
-	file *zip.File
-	io.ReadCloser
-}
-
-func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
-	if whence == 0 && offset == 0 {
-		r, err := f.file.Open()
-		if err != nil {
-			return 0, err
-		}
-		f.Close()
-		f.ReadCloser = r
-		return 0, nil
-	}
-	return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
-}
-
-func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
-	_, fi, err := fs.stat(abspath)
-	return fi, err
-}
-
-func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
-	_, fi, err := fs.stat(abspath)
-	return fi, err
-}
-
-func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
-	i, fi, err := fs.stat(abspath)
-	if err != nil {
-		return nil, err
-	}
-	if !fi.IsDir() {
-		return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
-	}
-
-	var list []os.FileInfo
-
-	// make dirname the prefix that file names must start with to be considered
-	// in this directory. we must special case the root directory because, per
-	// the spec of this package, zip file entries MUST NOT start with /, so we
-	// should not append /, as we would in every other case.
-	var dirname string
-	if isRoot(abspath) {
-		dirname = ""
-	} else {
-		zippath, err := zipPath(abspath)
-		if err != nil {
-			return nil, err
-		}
-		dirname = zippath + "/"
-	}
-	prevname := ""
-	for _, e := range fs.list[i:] {
-		if !strings.HasPrefix(e.Name, dirname) {
-			break // not in the same directory anymore
-		}
-		name := e.Name[len(dirname):] // local name
-		file := e
-		if i := strings.IndexRune(name, '/'); i >= 0 {
-			// We infer directories from files in subdirectories.
-			// If we have x/y, return a directory entry for x.
-			name = name[0:i] // keep local directory name only
-			file = nil
-		}
-		// If we have x/y and x/z, don't return two directory entries for x.
-		// TODO(gri): It should be possible to do this more efficiently
-		// by determining the (fs.list) range of local directory entries
-		// (via two binary searches).
-		if name != prevname {
-			list = append(list, zipFI{name, file})
-			prevname = name
-		}
-	}
-
-	return list, nil
-}
-
-func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
-	list := make(zipList, len(rc.File))
-	copy(list, rc.File) // sort a copy of rc.File
-	sort.Sort(list)
-	return &zipFS{rc, list, name}
-}
-
-type zipList []*zip.File
-
-// zipList implements sort.Interface
-func (z zipList) Len() int           { return len(z) }
-func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
-func (z zipList) Swap(i, j int)      { z[i], z[j] = z[j], z[i] }
-
-// lookup returns the smallest index of an entry with an exact match
-// for name, or an inexact match starting with name/. If there is no
-// such entry, the result is -1, false.
-func (z zipList) lookup(name string) (index int, exact bool) {
-	// look for exact match first (name comes before name/ in z)
-	i := sort.Search(len(z), func(i int) bool {
-		return name <= z[i].Name
-	})
-	if i >= len(z) {
-		return -1, false
-	}
-	// 0 <= i < len(z)
-	if z[i].Name == name {
-		return i, true
-	}
-
-	// look for inexact match (must be in z[i:], if present)
-	z = z[i:]
-	name += "/"
-	j := sort.Search(len(z), func(i int) bool {
-		return name <= z[i].Name
-	})
-	if j >= len(z) {
-		return -1, false
-	}
-	// 0 <= j < len(z)
-	if strings.HasPrefix(z[j].Name, name) {
-		return i + j, false
-	}
-
-	return -1, false
-}
diff --git a/cmd/golangorg/godoc/vfs/zipfs/zipfs_test.go b/cmd/golangorg/godoc/vfs/zipfs/zipfs_test.go
deleted file mode 100644
index 70899ad..0000000
--- a/cmd/golangorg/godoc/vfs/zipfs/zipfs_test.go
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2015 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.package zipfs
-package zipfs
-
-import (
-	"archive/zip"
-	"bytes"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-	"reflect"
-	"testing"
-
-	"golang.org/x/website/cmd/golangorg/godoc/vfs"
-)
-
-var (
-
-	// files to use to build zip used by zipfs in testing; maps path : contents
-	files = map[string]string{"foo": "foo", "bar/baz": "baz", "a/b/c": "c"}
-
-	// expected info for each entry in a file system described by files
-	tests = []struct {
-		Path      string
-		IsDir     bool
-		IsRegular bool
-		Name      string
-		Contents  string
-		Files     map[string]bool
-	}{
-		{"/", true, false, "", "", map[string]bool{"foo": true, "bar": true, "a": true}},
-		{"//", true, false, "", "", map[string]bool{"foo": true, "bar": true, "a": true}},
-		{"/foo", false, true, "foo", "foo", nil},
-		{"/foo/", false, true, "foo", "foo", nil},
-		{"/foo//", false, true, "foo", "foo", nil},
-		{"/bar", true, false, "bar", "", map[string]bool{"baz": true}},
-		{"/bar/", true, false, "bar", "", map[string]bool{"baz": true}},
-		{"/bar/baz", false, true, "baz", "baz", nil},
-		{"//bar//baz", false, true, "baz", "baz", nil},
-		{"/a/b", true, false, "b", "", map[string]bool{"c": true}},
-	}
-
-	// to be initialized in setup()
-	fs        vfs.FileSystem
-	statFuncs []statFunc
-)
-
-type statFunc struct {
-	Name string
-	Func func(string) (os.FileInfo, error)
-}
-
-func TestMain(t *testing.M) {
-	if err := setup(); err != nil {
-		fmt.Fprintf(os.Stderr, "Error setting up zipfs testing state: %v.\n", err)
-		os.Exit(1)
-	}
-	os.Exit(t.Run())
-}
-
-// setups state each of the tests uses
-func setup() error {
-	// create zipfs
-	b := new(bytes.Buffer)
-	zw := zip.NewWriter(b)
-	for file, contents := range files {
-		w, err := zw.Create(file)
-		if err != nil {
-			return err
-		}
-		_, err = io.WriteString(w, contents)
-		if err != nil {
-			return err
-		}
-	}
-	zw.Close()
-	zr, err := zip.NewReader(bytes.NewReader(b.Bytes()), int64(b.Len()))
-	if err != nil {
-		return err
-	}
-	rc := &zip.ReadCloser{
-		Reader: *zr,
-	}
-	fs = New(rc, "foo")
-
-	// pull out different stat functions
-	statFuncs = []statFunc{
-		{"Stat", fs.Stat},
-		{"Lstat", fs.Lstat},
-	}
-
-	return nil
-}
-
-func TestZipFSReadDir(t *testing.T) {
-	for _, test := range tests {
-		if test.IsDir {
-			infos, err := fs.ReadDir(test.Path)
-			if err != nil {
-				t.Errorf("Failed to read directory %v\n", test.Path)
-				continue
-			}
-			got := make(map[string]bool)
-			for _, info := range infos {
-				got[info.Name()] = true
-			}
-			if want := test.Files; !reflect.DeepEqual(got, want) {
-				t.Errorf("ReadDir %v got %v\nwanted %v\n", test.Path, got, want)
-			}
-		}
-	}
-}
-
-func TestZipFSStatFuncs(t *testing.T) {
-	for _, test := range tests {
-		for _, statFunc := range statFuncs {
-
-			// test can stat
-			info, err := statFunc.Func(test.Path)
-			if err != nil {
-				t.Errorf("Unexpected error using %v for %v: %v\n", statFunc.Name, test.Path, err)
-				continue
-			}
-
-			// test info.Name()
-			if got, want := info.Name(), test.Name; got != want {
-				t.Errorf("Using %v for %v info.Name() got %v wanted %v\n", statFunc.Name, test.Path, got, want)
-			}
-			// test info.IsDir()
-			if got, want := info.IsDir(), test.IsDir; got != want {
-				t.Errorf("Using %v for %v info.IsDir() got %v wanted %v\n", statFunc.Name, test.Path, got, want)
-			}
-			// test info.Mode().IsDir()
-			if got, want := info.Mode().IsDir(), test.IsDir; got != want {
-				t.Errorf("Using %v for %v info.Mode().IsDir() got %v wanted %v\n", statFunc.Name, test.Path, got, want)
-			}
-			// test info.Mode().IsRegular()
-			if got, want := info.Mode().IsRegular(), test.IsRegular; got != want {
-				t.Errorf("Using %v for %v info.Mode().IsRegular() got %v wanted %v\n", statFunc.Name, test.Path, got, want)
-			}
-			// test info.Size()
-			if test.IsRegular {
-				if got, want := info.Size(), int64(len(test.Contents)); got != want {
-					t.Errorf("Using %v for %v inf.Size() got %v wanted %v", statFunc.Name, test.Path, got, want)
-				}
-			}
-		}
-	}
-}
-
-func TestZipFSNotExist(t *testing.T) {
-	_, err := fs.Open("/does-not-exist")
-	if err == nil {
-		t.Fatalf("Expected an error.\n")
-	}
-	if !os.IsNotExist(err) {
-		t.Errorf("Expected an error satisfying os.IsNotExist: %v\n", err)
-	}
-}
-
-func TestZipFSOpenSeek(t *testing.T) {
-	for _, test := range tests {
-		if test.IsRegular {
-
-			// test Open()
-			f, err := fs.Open(test.Path)
-			if err != nil {
-				t.Error(err)
-				return
-			}
-			defer f.Close()
-
-			// test Seek() multiple times
-			for i := 0; i < 3; i++ {
-				all, err := ioutil.ReadAll(f)
-				if err != nil {
-					t.Error(err)
-					return
-				}
-				if got, want := string(all), test.Contents; got != want {
-					t.Errorf("File contents for %v got %v wanted %v\n", test.Path, got, want)
-				}
-				f.Seek(0, 0)
-			}
-		}
-	}
-}
-
-func TestRootType(t *testing.T) {
-	tests := []struct {
-		path   string
-		fsType vfs.RootType
-	}{
-		{"/src/net/http", vfs.RootTypeGoRoot},
-		{"/src/badpath", ""},
-		{"/", vfs.RootTypeGoRoot},
-	}
-
-	for _, item := range tests {
-		if fs.RootType(item.path) != item.fsType {
-			t.Errorf("unexpected fsType. Expected- %v, Got- %v", item.fsType, fs.RootType(item.path))
-		}
-	}
-}