gopls/integration: cosmetic/ergonomic updates

Some ergonomic and cosmetic updates are made to the integration/replay
and integration/parse packages, following a pass through the code:
 + In the README, a some typos are corrected.
 + A few symbols are renamed:
   - Direction->MsgType, since this type is more than a client-server
     direction
   - logRec->scanLogs, to be more consistent with the naming convention
     SplitFuncs in the bufio package
   - CamelCase is applied in a few places.
 + Context is plumbed through, rather than use a package local ctx.
 + In a few cases, returning an error is preferred over log.Fatal.
 + Some duplicated code is deleted, and types from the parse package are
   reused. (this was probably cruft from the repo migration)
 + The logfile is made an argument rather than a flag to the replay
   tool.

Change-Id: Ie72e6e8a4d7020d8cf708f6791353897791bcc86
Reviewed-on: https://go-review.googlesource.com/c/tools/+/211057
Reviewed-by: Peter Weinberger <pjw@google.com>
diff --git a/gopls/integration/parse/parse.go b/gopls/integration/parse/parse.go
index ae89957..0200c42 100644
--- a/gopls/integration/parse/parse.go
+++ b/gopls/integration/parse/parse.go
@@ -9,37 +9,37 @@
 import (
 	"bufio"
 	"encoding/json"
+	"errors"
 	"fmt"
-	"io"
 	"log"
 	"os"
 	"regexp"
 	"strings"
 )
 
-// Direction is the type of message,
-type Direction int
+// MsgType is the type of message.
+type MsgType int
 
 const (
-	// Clrequest from client to server has method and id
-	Clrequest Direction = iota
-	// Clresponse from server to client
-	Clresponse
-	// Svrequest from server to client, has method and id
-	Svrequest
-	// Svresponse from client to server
-	Svresponse
-	// Toserver notification has method, but no id
-	Toserver
-	// Toclient notification
-	Toclient
-	// Reporterr is an error message
-	Reporterr // errors have method and id
+	// ClRequest from client to server has method and id
+	ClRequest MsgType = iota
+	// ClResponse from server to client
+	ClResponse
+	// SvRequest from server to client, has method and id
+	SvRequest
+	// SvResponse from client to server
+	SvResponse
+	// ToServer notification has method, but no id
+	ToServer
+	// ToClient notification
+	ToClient
+	// ReportErr is an error message
+	ReportErr // errors have method and id
 )
 
-// Logmsg is the type of a parsed log entry
+// Logmsg is the type of a parsed log entry.
 type Logmsg struct {
-	Dir     Direction
+	Type    MsgType
 	Method  string
 	ID      string      // for requests/responses. Client and server request ids overlap
 	Elapsed string      // for responses
@@ -48,7 +48,7 @@
 	Body    interface{} // the parsed result
 }
 
-// ReadLogs from a file. Most users should use TRlog().
+// ReadLogs from a file. Most users should use ToRlog().
 func ReadLogs(fname string) ([]*Logmsg, error) {
 	byid := make(map[string]int)
 	msgs := []*Logmsg{}
@@ -56,39 +56,39 @@
 	if err != nil {
 		return nil, err
 	}
+	defer fd.Close()
 	logrdr := bufio.NewScanner(fd)
 	logrdr.Buffer(nil, 1<<25) //  a large buffer, for safety
-	logrdr.Split(logRec)
+	logrdr.Split(scanLogs)
 	for i := 0; logrdr.Scan(); i++ {
 		flds := strings.SplitN(logrdr.Text(), "\n", 2)
 		if len(flds) == 1 {
 			flds = append(flds, "") // for Errors
 		}
-		msg := parselog(flds[0], flds[1])
-		if msg == nil {
-			log.Fatalf("failed to parse %q", logrdr.Text())
-			continue
+		msg, err := parselog(flds[0], flds[1])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse %q: %v", logrdr.Text(), err)
 		}
-		switch msg.Dir {
-		case Clrequest, Svrequest:
+		switch msg.Type {
+		case ClRequest, SvRequest:
 			v, err := msg.unmarshal(Requests(msg.Method))
 			if err != nil {
-				log.Fatalf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
+				return nil, fmt.Errorf("%v for %s, %T", err, msg.Method, Requests(msg.Method))
 			}
 			msg.Body = v
-		case Clresponse, Svresponse:
+		case ClResponse, SvResponse:
 			v, err := msg.doresponse()
 			if err != nil {
-				log.Fatalf("%v %s", err, msg.Method)
+				return nil, fmt.Errorf("%v %s", err, msg.Method)
 			}
 			msg.Body = v
-		case Toserver, Toclient:
+		case ToServer, ToClient:
 			v, err := msg.unmarshal(Notifs(msg.Method))
 			if err != nil && Notifs(msg.Method) != nil {
-				log.Fatalf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
+				return nil, fmt.Errorf("%s/%T: %v", msg.Method, Notifs(msg.Method), err)
 			}
 			msg.Body = v
-		case Reporterr:
+		case ReportErr:
 			msg.Body = msg.ID // cause?
 		}
 		byid[msg.ID]++
@@ -101,13 +101,13 @@
 }
 
 // parse a single log message, given first line, and the rest
-func parselog(first, rest string) *Logmsg {
+func parselog(first, rest string) (*Logmsg, error) {
 	if strings.HasPrefix(rest, "Params: ") {
 		rest = rest[8:]
 	} else if strings.HasPrefix(rest, "Result: ") {
 		rest = rest[8:]
 	}
-	ans := &Logmsg{Hdr: first, Rest: rest}
+	msg := &Logmsg{Hdr: first, Rest: rest}
 	fixid := func(s string) string {
 		// emacs does (n)., gopls does (n)'.
 		s = strings.Trim(s, "()'.{)")
@@ -118,45 +118,44 @@
 	// gopls and emacs differ in how they report elapsed time
 	switch {
 	case chk("Sending request", 9):
-		ans.Dir = Clrequest
-		ans.Method = flds[6][1:]
-		ans.ID = fixid(flds[8][:len(flds[8])-2])
+		msg.Type = ClRequest
+		msg.Method = flds[6][1:]
+		msg.ID = fixid(flds[8][:len(flds[8])-2])
 	case chk("Received response", 11):
-		ans.Dir = Clresponse
-		ans.Method = flds[6][1:]
-		ans.ID = fixid(flds[8])
-		ans.Elapsed = flds[10]
+		msg.Type = ClResponse
+		msg.Method = flds[6][1:]
+		msg.ID = fixid(flds[8])
+		msg.Elapsed = flds[10]
 	case chk("Received request", 9):
-		ans.Dir = Svrequest
-		ans.Method = flds[6][1:]
-		ans.ID = fixid(flds[8])
+		msg.Type = SvRequest
+		msg.Method = flds[6][1:]
+		msg.ID = fixid(flds[8])
 	case chk("Sending response", 11), // gopls
 		chk("Sending response", 13): // emacs
-		ans.Dir = Svresponse
-		ans.Method = flds[6][1:]
-		ans.ID = fixid(flds[8][:len(flds[8])-1])
-		ans.Elapsed = flds[10]
+		msg.Type = SvResponse
+		msg.Method = flds[6][1:]
+		msg.ID = fixid(flds[8][:len(flds[8])-1])
+		msg.Elapsed = flds[10]
 	case chk("Sending notification", 7):
-		ans.Dir = Toserver
-		ans.Method = strings.Trim(flds[6], ".'")
+		msg.Type = ToServer
+		msg.Method = strings.Trim(flds[6], ".'")
 		if len(flds) == 9 {
-			log.Printf("len=%d method=%s %q", len(flds), ans.Method, first)
+			log.Printf("len=%d method=%s %q", len(flds), msg.Method, first)
 		}
 	case chk("Received notification", 7):
-		ans.Dir = Toclient
-		ans.Method = flds[6][1 : len(flds[6])-2]
+		msg.Type = ToClient
+		msg.Method = flds[6][1 : len(flds[6])-2]
 	case strings.HasPrefix(first, "[Error - "):
-		ans.Dir = Reporterr
+		msg.Type = ReportErr
 		both := flds[5]
 		idx := strings.Index(both, "#") // relies on ID.Number
-		ans.Method = both[:idx]
-		ans.ID = fixid(both[idx+1:])
-		ans.Rest = strings.Join(flds[6:], " ")
+		msg.Method = both[:idx]
+		msg.ID = fixid(both[idx+1:])
+		msg.Rest = strings.Join(flds[6:], " ")
 	default:
-		log.Fatalf("surprise, first=%q with %d flds", first, len(flds))
-		return nil
+		return nil, fmt.Errorf("surprise, first=%q with %d flds", first, len(flds))
 	}
-	return ans
+	return msg, nil
 }
 
 // unmarshal into a proposed type
@@ -197,33 +196,33 @@
 var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
 
 // return offset of start of next record, contents of record, error
-func logRec(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
+func scanLogs(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
 	got := recSep.FindIndex(b)
 	if got == nil {
-		if !atEOF {
-			return 0, nil, nil // need more
+		if atEOF && len(b) > 0 {
+			return 0, nil, errors.New("malformed log: all logs should end with a separator")
 		}
-		return 0, nil, io.EOF
+		return 0, nil, nil
 	}
 	return got[1], b[:got[0]], nil
 }
 
 // String returns a user-useful versin of a Direction
-func (d Direction) String() string {
+func (d MsgType) String() string {
 	switch d {
-	case Clrequest:
+	case ClRequest:
 		return "clrequest"
-	case Clresponse:
+	case ClResponse:
 		return "clresponse"
-	case Svrequest:
+	case SvRequest:
 		return "svrequest"
-	case Svresponse:
+	case SvResponse:
 		return "svresponse"
-	case Toserver:
+	case ToServer:
 		return "toserver"
-	case Toclient:
+	case ToClient:
 		return "toclient"
-	case Reporterr:
+	case ReportErr:
 		return "reporterr"
 	}
 	return fmt.Sprintf("dirname: %d unknown", d)
diff --git a/gopls/integration/parse/rlog.go b/gopls/integration/parse/rlog.go
index 00cd16c..58e752c 100644
--- a/gopls/integration/parse/rlog.go
+++ b/gopls/integration/parse/rlog.go
@@ -50,28 +50,28 @@
 	}
 	ans := newRlog(x)
 	for _, l := range x {
-		switch l.Dir {
-		case Clrequest:
+		switch l.Type {
+		case ClRequest:
 			ans.ServerCall[l.ID] = l
-		case Clresponse:
+		case ClResponse:
 			ans.ServerReply[l.ID] = l
-			if l.Dir != Reporterr {
+			if l.Type != ReportErr {
 				n := 0
 				fmt.Sscanf(l.Elapsed, "%d", &n)
 				ans.Histogram.add(n)
 			}
-		case Svrequest:
+		case SvRequest:
 			ans.ClientCall[l.ID] = l
-		case Svresponse:
+		case SvResponse:
 			ans.ClientReply[l.ID] = l
-		case Toclient:
+		case ToClient:
 			ans.ClientNotifs = append(ans.ClientNotifs, l)
-		case Toserver:
+		case ToServer:
 			ans.ServerNotifs = append(ans.ServerNotifs, l)
-		case Reporterr:
+		case ReportErr:
 			ans.ServerReply[l.ID] = l
 		default:
-			log.Fatalf("eh? %s/%s (%s)", l.Dir, l.Method, l.ID)
+			log.Fatalf("eh? %s/%s (%s)", l.Type, l.Method, l.ID)
 		}
 	}
 	return ans, nil
diff --git a/gopls/integration/replay/README.md b/gopls/integration/replay/README.md
index 3b29538..189b5b1 100644
--- a/gopls/integration/replay/README.md
+++ b/gopls/integration/replay/README.md
@@ -1,6 +1,6 @@
 # Replaying Logs
 
-The LSP log replayer takes a log from a gopls session, starts up an instance of goppls,
+The LSP log replayer takes a log from a gopls session, starts up an instance of gopls,
 and tries to replay the session. It produces a log from the replayed session and reports
 some comparative statistics of the two logs.
 
@@ -48,7 +48,7 @@
 the same number of errors *reporterr*. (That's mysterious, but a look at the ends of the log files shows
 that the original session ended with several RPCs that don't show up, for whatever reason, in the new session.)
 
-Finally, there are counts of the various nofications seen, in the new log and the old log, and
+Finally, there are counts of the various notifications seen, in the new log and the old log, and
 which direction they went. (The 3 fewer notifications in the summary above can be seen here to be from cancels
 and a didChange.)
 ```
@@ -76,4 +76,4 @@
 Gopls invokes various tools, and the environment they see could have changed too.
 
 Replay will use the gopls it finds (or is given). It has no way of using
-the same version that created the original session.
\ No newline at end of file
+the same version that created the original session.
diff --git a/gopls/integration/replay/main.go b/gopls/integration/replay/main.go
index 6559418..cd30dd6 100644
--- a/gopls/integration/replay/main.go
+++ b/gopls/integration/replay/main.go
@@ -11,12 +11,9 @@
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io"
 	"log"
 	"os"
 	"os/exec"
-	"regexp"
-	"runtime"
 	"sort"
 	"strconv"
 	"strings"
@@ -27,47 +24,52 @@
 )
 
 var (
-	ctx     = context.Background()
 	command = flag.String("cmd", "", "location of server to send to, looks for gopls")
-	logf    = flag.String("log", "", "log file to replay")
 	cmp     = flag.Bool("cmp", false, "only compare log and /tmp/seen")
 	logrdr  *bufio.Scanner
 	msgs    []*parse.Logmsg
 	// requests and responses/errors, by id
-	clreq  = make(map[string]*logmsg)
-	clresp = make(map[string]*logmsg)
-	svreq  = make(map[string]*logmsg)
-	svresp = make(map[string]*logmsg)
+	clreq  = make(map[string]*parse.Logmsg)
+	clresp = make(map[string]*parse.Logmsg)
+	svreq  = make(map[string]*parse.Logmsg)
+	svresp = make(map[string]*parse.Logmsg)
 )
 
 func main() {
 	log.SetFlags(log.Lshortfile)
+	flag.Usage = func() {
+		fmt.Fprintln(flag.CommandLine.Output(), "replay [options] <logfile>")
+		flag.PrintDefaults()
+	}
 	flag.Parse()
-	if *logf == "" {
-		log.Fatal("need -log")
+	if flag.NArg() != 1 {
+		flag.Usage()
+		os.Exit(2)
 	}
+	logf := flag.Arg(0)
 
-	orig, err := parse.ToRlog(*logf)
+	orig, err := parse.ToRlog(logf)
 	if err != nil {
-		log.Fatalf("logfile %q %v", *logf, err)
+		log.Fatalf("error parsing logfile %q: %v", logf, err)
 	}
+	ctx := context.Background()
 	msgs = orig.Logs
 	log.Printf("old %d, hist:%s", len(msgs), orig.Histogram)
 
 	if !*cmp {
 		log.Print("calling mimic")
-		mimic()
+		mimic(ctx)
 	}
 	seen, err := parse.ToRlog("/tmp/seen")
 	if err != nil {
 		log.Fatal(err)
 	}
-	vvv := seen.Logs
-	log.Printf("new %d, hist:%s", len(vvv), seen.Histogram)
+	newMsgs := seen.Logs
+	log.Printf("new %d, hist:%s", len(newMsgs), seen.Histogram)
 
 	ok := make(map[string]int)
 	f := func(x []*parse.Logmsg, label string, diags map[string][]p.Diagnostic) {
-		cnts := make(map[parse.Direction]int)
+		counts := make(map[parse.MsgType]int)
 		for _, l := range x {
 			if l.Method == "window/logMessage" {
 				// don't care
@@ -80,12 +82,12 @@
 				}
 				diags[v.URI] = v.Diagnostics
 			}
-			cnts[l.Dir]++
+			counts[l.Type]++
 			// notifications only
-			if l.Dir != parse.Toserver && l.Dir != parse.Toclient {
+			if l.Type != parse.ToServer && l.Type != parse.ToClient {
 				continue
 			}
-			s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Dir)
+			s := fmt.Sprintf("%s %s %s", strings.Replace(l.Hdr, "\r", "", -1), label, l.Type)
 			if i := strings.Index(s, "notification"); i != -1 {
 				s = s[i+12:]
 			}
@@ -95,15 +97,15 @@
 			ok[s]++
 		}
 		msg := ""
-		for i := parse.Clrequest; i <= parse.Reporterr; i++ {
-			msg += fmt.Sprintf("%s:%d ", i, cnts[i])
+		for i := parse.ClRequest; i <= parse.ReportErr; i++ {
+			msg += fmt.Sprintf("%s:%d ", i, counts[i])
 		}
 		log.Printf("%s: %s", label, msg)
 	}
 	mdiags := make(map[string][]p.Diagnostic)
 	f(msgs, "old", mdiags)
 	vdiags := make(map[string][]p.Diagnostic)
-	f(vvv, "new", vdiags)
+	f(newMsgs, "new", vdiags)
 	buf := []string{}
 	for k := range ok {
 		buf = append(buf, fmt.Sprintf("%s %d", k, ok[k]))
@@ -137,74 +139,37 @@
 	}
 }
 
-type direction int // what sort of message it is
-const (
-	// rpc from client to server have method and id
-	clrequest direction = iota
-	clresponse
-	// rpc from server have method and id
-	svrequest
-	svresponse
-	// notifications have method, but no id
-	toserver
-	toclient
-	reporterr // errors have method and id
-)
-
-// clrequest has method and id. toserver has method but no id, and svresponse has result (and id)
-type logmsg struct {
-	dir     direction
-	method  string
-	id      string      // for requests/responses. Client and server request ids overlap
-	elapsed string      // for responses
-	hdr     string      // do we need to keep all these strings?
-	rest    string      // the unparsed result, with newlines or not
-	body    interface{} // the parsed(?) result
-}
-
-// combined has all the fields of both Request and Response.
-// Unmarshal this and then work out which it is.
-type combined struct {
-	VersionTag jsonrpc2.VersionTag `json:"jsonrpc"`
-	ID         *jsonrpc2.ID        `json:"id,omitempty"`
-	// RPC name
-	Method string           `json:"method"`
-	Params *json.RawMessage `json:"params,omitempty"`
-	Result *json.RawMessage `json:"result,omitempty"`
-	Error  *jsonrpc2.Error  `json:"error,omitempty"`
-}
-
-func (c *combined) dir() direction {
+func msgType(c *p.Combined) parse.MsgType {
 	// Method, Params, ID => request
 	// Method, Params, no-ID => notification
 	// Error => error response
 	// Result, ID => response
 	if c.Error != nil {
-		return reporterr
+		return parse.ReportErr
 	}
 	if c.Params != nil && c.ID != nil {
 		// $/cancel could be either, cope someday
 		if parse.FromServer(c.Method) {
-			return svrequest
+			return parse.SvRequest
 		}
-		return clrequest
+		return parse.ClRequest
 	}
 	if c.Params != nil {
-		// we're receiving it, so it must be toclient
-		return toclient
+		// we're receiving it, so it must be ToClient
+		return parse.ToClient
 	}
 	if c.Result == nil {
 		if c.ID != nil {
-			return clresponse
+			return parse.ClResponse
 		}
 		log.Printf("%+v", *c)
 		panic("couldn't determine direction")
 	}
-	// we've received it, so it must be clresponse
-	return clresponse
+	// we've received it, so it must be ClResponse
+	return parse.ClResponse
 }
 
-func send(l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
+func send(ctx context.Context, l *parse.Logmsg, stream jsonrpc2.Stream, id *jsonrpc2.ID) {
 	x, err := json.Marshal(l.Body)
 	if err != nil {
 		log.Fatal(err)
@@ -219,25 +184,25 @@
 		id = &jsonrpc2.ID{Number: int64(n)}
 	}
 	var r interface{}
-	switch l.Dir {
-	case parse.Clrequest:
+	switch l.Type {
+	case parse.ClRequest:
 		r = jsonrpc2.WireRequest{
 			ID:     id,
 			Method: l.Method,
 			Params: &y,
 		}
-	case parse.Svresponse:
+	case parse.SvResponse:
 		r = jsonrpc2.WireResponse{
 			ID:     id,
 			Result: &y,
 		}
-	case parse.Toserver:
+	case parse.ToServer:
 		r = jsonrpc2.WireRequest{
 			Method: l.Method,
 			Params: &y,
 		}
 	default:
-		log.Fatalf("sending %s", l.Dir)
+		log.Fatalf("sending %s", l.Type)
 	}
 	data, err := json.Marshal(r)
 	if err != nil {
@@ -254,15 +219,15 @@
 	return strconv.Itoa(int(x.Number))
 }
 
-func respond(c *combined, stream jsonrpc2.Stream) {
+func respond(ctx context.Context, c *p.Combined, stream jsonrpc2.Stream) {
 	// c is a server request
 	// pick out the id, and look for the response in msgs
 	id := strID(c.ID)
 	for _, l := range msgs {
-		if l.ID == id && l.Dir == parse.Svresponse {
+		if l.ID == id && l.Type == parse.SvResponse {
 			// check that the methods match?
 			// need to send back the same ID we got.
-			send(l, stream, c.ID)
+			send(ctx, l, stream, c.ID)
 			return
 		}
 	}
@@ -287,14 +252,16 @@
 	for _, t := range totry {
 		g := os.Getenv(t[0])
 		if g != "" && ok(g+t[1]) {
-			return g + t[1]
+			gopls := g + t[1]
+			log.Printf("using gopls at %s", gopls)
+			return gopls
 		}
 	}
 	log.Fatal("could not find gopls")
 	return ""
 }
 
-func mimic() {
+func mimic(ctx context.Context) {
 	log.Printf("mimic %d", len(msgs))
 	if *command == "" {
 		*command = findgopls()
@@ -313,7 +280,7 @@
 		log.Fatal(err)
 	}
 	stream := jsonrpc2.NewHeaderStream(fromServer, toServer)
-	rchan := make(chan *combined, 10) // do we need buffering?
+	rchan := make(chan *p.Combined, 10) // do we need buffering?
 	rdr := func() {
 		for {
 			buf, _, err := stream.Read(ctx)
@@ -321,7 +288,7 @@
 				rchan <- nil // close it instead?
 				return
 			}
-			msg := &combined{}
+			msg := &p.Combined{}
 			if err := json.Unmarshal(buf, msg); err != nil {
 				log.Fatal(err)
 			}
@@ -331,16 +298,16 @@
 	go rdr()
 	// send as many as possible: all clrequests and toservers up to a clresponse
 	// and loop
-	seenids := make(map[string]bool) // id's that have been responded toig:
+	seenids := make(map[string]bool) // id's that have been responded to:
 big:
 	for _, l := range msgs {
-		switch l.Dir {
-		case parse.Toserver: // just send these as we get to them
-			send(l, stream, nil)
-		case parse.Clrequest:
-			send(l, stream, nil) // for now, wait for a response, to make sure code is ok
+		switch l.Type {
+		case parse.ToServer: // just send these as we get to them
+			send(ctx, l, stream, nil)
+		case parse.ClRequest:
+			send(ctx, l, stream, nil) // for now, wait for a response, to make sure code is ok
 			fallthrough
-		case parse.Clresponse, parse.Reporterr: // don't go past these until they're received
+		case parse.ClResponse, parse.ReportErr: // don't go past these until they're received
 			if seenids[l.ID] {
 				break // onward, as it has been received already
 			}
@@ -353,222 +320,28 @@
 				// if it's svrequest, do something
 				// if it's clresponse or reporterr, add to seenids, and if it
 				// is l.id, break out of the loop, and continue the outer loop
-				switch x.dir() {
-				case svrequest:
-					respond(x, stream)
+				switch mt := msgType(x); mt {
+				case parse.SvRequest:
+					respond(ctx, x, stream)
 					continue done // still waiting
-				case clresponse, reporterr:
+				case parse.ClResponse, parse.ReportErr:
 					id := strID(x.ID)
 					seenids[id] = true
 					if id == l.ID {
 						break done
 					}
-				case toclient:
+				case parse.ToClient:
 					continue
 				default:
-					log.Fatalf("%s", x.dir())
+					log.Fatalf("%s", mt)
 				}
 			}
-		case parse.Svrequest: // not ours to send
+		case parse.SvRequest: // not ours to send
 			continue
-		case parse.Svresponse: // sent by us, if the request arrives
+		case parse.SvResponse: // sent by us, if the request arrives
 			continue
-		case parse.Toclient: // we don't send these
+		case parse.ToClient: // we don't send these
 			continue
 		}
 	}
 }
-
-func readLogs(fname string) []*logmsg {
-	byid := make(map[string]int)
-	msgs := []*logmsg{}
-	fd, err := os.Open(fname)
-	if err != nil {
-		log.Fatal(err)
-	}
-	logrdr = bufio.NewScanner(fd)
-	logrdr.Buffer(nil, 1<<25) //  a large buffer, for safety
-	logrdr.Split(logRec)
-	for i := 0; logrdr.Scan(); i++ {
-		flds := strings.SplitN(logrdr.Text(), "\n", 2)
-		if len(flds) == 1 {
-			flds = append(flds, "") // for Errors
-		}
-		msg := parselog(flds[0], flds[1])
-		if msg == nil {
-			log.Fatalf("failed to parse %q", logrdr.Text())
-			continue
-		}
-		switch msg.dir {
-		case clrequest, svrequest:
-			v, err := msg.unmarshal(parse.Requests(msg.method))
-			if err != nil {
-				log.Fatal(err)
-			}
-			msg.body = v
-		case clresponse, svresponse:
-			v, err := msg.doresponse()
-			if err != nil {
-				log.Fatalf("%v %s", err, msg.method)
-			}
-			msg.body = v
-		case toserver, toclient:
-			v, err := msg.unmarshal(parse.Notifs(msg.method))
-			if err != nil {
-				log.Fatal(err)
-			}
-			msg.body = v
-		case reporterr:
-			msg.body = msg.id // cause?
-		}
-		byid[msg.id]++
-		msgs = append(msgs, msg)
-	}
-	if err = logrdr.Err(); err != nil {
-		log.Fatal(err)
-		return msgs
-	}
-	// there's 2 uses of id 1, and notifications have no id
-	for k, v := range byid {
-		if false && v != 2 {
-			log.Printf("ids %s:%d", k, v)
-		}
-	}
-	if false {
-		var m runtime.MemStats
-		runtime.ReadMemStats(&m)
-		log.Printf("%d msgs, alloc=%d HeapAlloc=%d", len(msgs), m.Alloc, m.HeapAlloc)
-	}
-	return msgs
-}
-
-func (d direction) String() string {
-	switch d {
-	case clrequest:
-		return "clrequest"
-	case clresponse:
-		return "clresponse"
-	case svrequest:
-		return "svrequest"
-	case svresponse:
-		return "svresponse"
-	case toserver:
-		return "toserver"
-	case toclient:
-		return "toclient"
-	case reporterr:
-		return "reporterr"
-	}
-	return fmt.Sprintf("dirname: %d unknown", d)
-}
-
-func (l *logmsg) Short() string {
-	return fmt.Sprintf("%s %s %s %s", l.dir, l.method, l.id, l.elapsed)
-}
-
-func (l *logmsg) unmarshal(p interface{}) (interface{}, error) {
-	r := []byte(l.rest)
-	if err := json.Unmarshal(r, p); err != nil {
-		// need general alternatives, but for now
-		// if p is *[]foo and rest is {}, return an empty p (or *p?)
-		// or, cheat:
-		if l.rest == "{}" {
-			return nil, nil
-		}
-		return nil, err
-	}
-	return p, nil
-}
-
-func (l *logmsg) doresponse() (interface{}, error) {
-	for _, x := range parse.Responses(l.method) {
-		v, err := l.unmarshal(x)
-		if err == nil {
-			return v, nil
-		}
-		if x == nil {
-			return new(interface{}), nil
-		}
-	}
-	log.Fatalf("doresponse failed for %s", l.method)
-	return nil, nil
-}
-
-// parse a single log message, given first line, and the rest
-func parselog(first, rest string) *logmsg {
-	if strings.HasPrefix(rest, "Params: ") {
-		rest = rest[8:]
-	} else if strings.HasPrefix(rest, "Result: ") {
-		rest = rest[8:]
-	}
-	ans := &logmsg{hdr: first, rest: rest}
-	fixid := func(s string) string {
-		if s != "" && s[0] == '(' {
-			s = s[1 : len(s)-1]
-		}
-		return s
-	}
-	flds := strings.Fields(first)
-	chk := func(s string, n int) bool { return strings.Contains(first, s) && len(flds) == n }
-	// gopls and emacs differ in how they report elapsed time
-	switch {
-	case chk("Sending request", 9):
-		ans.dir = clrequest
-		ans.method = flds[6][1:]
-		ans.id = fixid(flds[8][:len(flds[8])-2])
-		clreq[ans.id] = ans
-	case chk("Received response", 11):
-		ans.dir = clresponse
-		ans.method = flds[6][1:]
-		ans.id = fixid(flds[8][:len(flds[8])-1])
-		ans.elapsed = flds[10]
-		clresp[ans.id] = ans
-	case chk("Received request", 9):
-		ans.dir = svrequest
-		ans.method = flds[6][1:]
-		ans.id = fixid(flds[8][:len(flds[8])-2])
-		svreq[ans.id] = ans
-	case chk("Sending response", 11), // gopls
-		chk("Sending response", 13): // emacs
-		ans.dir = svresponse
-		ans.method = flds[6][1:]
-		ans.id = fixid(flds[8][:len(flds[8])-1])
-		ans.elapsed = flds[10]
-		svresp[ans.id] = ans
-	case chk("Sending notification", 7):
-		ans.dir = toserver
-		ans.method = strings.Trim(flds[6], ".'")
-		if len(flds) == 9 {
-			log.Printf("len=%d method=%s %q", len(flds), ans.method, first)
-		}
-	case chk("Received notification", 7):
-		ans.dir = toclient
-		ans.method = flds[6][1 : len(flds[6])-2]
-	case strings.HasPrefix(first, "[Error - "):
-		ans.dir = reporterr
-		both := flds[5]
-		idx := strings.Index(both, "#") // relies on ID.Number
-		ans.method = both[:idx]
-		ans.id = fixid(both[idx+1:])
-		ans.rest = strings.Join(flds[6:], " ")
-		clreq[ans.id] = ans
-	default:
-		log.Fatalf("surprise, first=%q with %d flds", first, len(flds))
-		return nil
-	}
-	return ans
-}
-
-var recSep = regexp.MustCompile("\n\n\n|\r\n\r\n\r\n")
-
-// return start of next record, contents of record, error
-func logRec(b []byte, atEOF bool) (int, []byte, error) { //bufio.SplitFunc
-	got := recSep.FindIndex(b)
-	if got == nil {
-		if !atEOF {
-			return 0, nil, nil // need more
-		}
-		return 0, nil, io.EOF
-	}
-	return got[1], b[:got[0]], nil
-}