func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) {
	abspath := f
	data, err := vfs.ReadFile(fs, abspath)
	if err != nil {
		log.Print(err)
		pres.ServeError(w, r, f, err)
		return
	}
	lo, _ := strconv.Atoi(r.FormValue("lo"))
	hi, _ := strconv.Atoi(r.FormValue("hi"))
	if hi < lo {
		hi = lo
	}
	lo = lineToByte(data, lo)
	hi = lineToByte(data, hi+1)
	n := 4
	mark := lo
	for ; mark > 0 && n > 0; mark-- {
		if data[mark-1] == '\n' {
			if n--; n == 0 {
				break
			}
		}
	}
	io.WriteString(w, `<style type="text/css">@import "/doc/codewalk/codewalk.css";</style><pre>`)
	template.HTMLEscape(w, data[0:mark])
	io.WriteString(w, "<a name='mark'></a>")
	template.HTMLEscape(w, data[mark:lo])
	if lo < hi {
		io.WriteString(w, "<div class='codewalkhighlight'>")
		template.HTMLEscape(w, data[lo:hi])
		io.WriteString(w, "</div>")
	}
	template.HTMLEscape(w, data[hi:])
	io.WriteString(w, "</pre>")
}
codewalkFileprint serves requests with ?fileprint=f&lo=lo&hi=hi.
The filename f has already been retrieved and is passed as an argument.
Lo and hi are the numbers of the first and last line to highlight
in the response.  This format is used for the middle window pane
of the codewalk pages.  It is a separate iframe and does not get
the usual godoc HTML wrapper.
package main
import (
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	pathpkg "path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"unicode/utf8"
	"golang.org/x/tools/godoc"
	"golang.org/x/tools/godoc/vfs"
)
var codewalkHTML, codewalkdirHTML *template.Template
func codewalk(w http.ResponseWriter, r *http.Request) {
	relpath := r.URL.Path[len("/doc/codewalk/"):]
	abspath := r.URL.Path
	r.ParseForm()
	if f := r.FormValue("fileprint"); f != "" {
		codewalkFileprint(w, r, f)
		return
	}
	dir, err := fs.Lstat(abspath)
	if err == nil && dir.IsDir() {
		codewalkDir(w, r, relpath, abspath)
		return
	}
	if err == nil {
		pres.ServeFile(w, r)
		return
	}
	abspath = strings.TrimRight(abspath, "/")
	cw, err := loadCodewalk(abspath + ".xml")
	if err != nil {
		log.Print(err)
		pres.ServeError(w, r, relpath, err)
		return
	}
	if redir(w, r) {
		return
	}
	pres.ServePage(w, godoc.Page{
		Title:    "Codewalk: " + cw.Title,
		Tabtitle: cw.Title,
		Body:     applyTemplate(codewalkHTML, "codewalk", cw),
	})
}
func redir(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 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 Codewalk struct {
	Title string      `xml:"title,attr"`
	File  []string    `xml:"file"`
	Step  []*Codestep `xml:"step"`
}
type Codestep struct {
	Src   string `xml:"src,attr"`
	Title string `xml:"title,attr"`
	XML   string `xml:",innerxml"`
	Err    error
	File   string
	Lo     int
	LoByte int
	Hi     int
	HiByte int
	Data   []byte
}
func (st *Codestep) String() string {
	s := st.File
	if st.Lo != 0 || st.Hi != 0 {
		s += fmt.Sprintf(":%d", st.Lo)
		if st.Lo != st.Hi {
			s += fmt.Sprintf(",%d", st.Hi)
		}
	}
	return s
}
func loadCodewalk(filename string) (*Codewalk, error) {
	f, err := fs.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	cw := new(Codewalk)
	d := xml.NewDecoder(f)
	d.Entity = xml.HTMLEntity
	err = d.Decode(cw)
	if err != nil {
		return nil, &os.PathError{Op: "parsing", Path: filename, Err: err}
	}
	m := make(map[string]bool)
	for _, st := range cw.Step {
		i := strings.Index(st.Src, ":")
		if i < 0 {
			i = len(st.Src)
		}
		filename := st.Src[0:i]
		data, err := vfs.ReadFile(fs, filename)
		if err != nil {
			st.Err = err
			continue
		}
		if i < len(st.Src) {
			lo, hi, err := addrToByteRange(st.Src[i+1:], 0, data)
			if err != nil {
				st.Err = err
				continue
			}
			for lo > 0 && data[lo-1] != '\n' {
				lo--
			}
			for hi < len(data) && (hi == 0 || data[hi-1] != '\n') {
				hi++
			}
			st.Lo = byteToLine(data, lo)
			st.Hi = byteToLine(data, hi-1)
		}
		st.Data = data
		st.File = filename
		m[filename] = true
	}
	cw.File = make([]string, len(m))
	i := 0
	for f := range m {
		cw.File[i] = f
		i++
	}
	sort.Strings(cw.File)
	return cw, nil
}
func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string) {
	type elem struct {
		Name  string
		Title string
	}
	dir, err := fs.ReadDir(abspath)
	if err != nil {
		log.Print(err)
		pres.ServeError(w, r, relpath, err)
		return
	}
	var v []interface{}
	for _, fi := range dir {
		name := fi.Name()
		if fi.IsDir() {
			v = append(v, &elem{name + "/", ""})
		} else if strings.HasSuffix(name, ".xml") {
			cw, err := loadCodewalk(abspath + "/" + name)
			if err != nil {
				continue
			}
			v = append(v, &elem{name[0 : len(name)-len(".xml")], cw.Title})
		}
	}
	pres.ServePage(w, godoc.Page{
		Title: "Codewalks",
		Body:  applyTemplate(codewalkdirHTML, "codewalkdir", v),
	})
}