func newVFSHandler(ar *zip.ReadCloser) jsonrpc2HandlerFunc {
	return func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
		switch req.Method {
		case "workspace/xfiles":
			var results []lsp.TextDocumentIdentifier
			for _, f := range ar.File {
				results = append(results, lsp.TextDocumentIdentifier{URI: lsp.DocumentURI("file://" + f.Name)})
			}
			return results, nil
		case "textDocument/xcontent":
			var params struct {
				TextDocument lsp.TextDocumentIdentifier `json:"textDocument"`
			}
			if err := json.Unmarshal(*req.Params, &params); err != nil {
				return nil, err
			}
			u, err := url.Parse(string(params.TextDocument.URI))
			if err != nil {
				return nil, err
			}
			for _, f := range ar.File {
				if f.Name == u.Path {
					rc, err := f.Open()
					if err != nil {
						return nil, err
					}
					defer rc.Close()
					b, err := ioutil.ReadAll(rc)
					if err != nil {
						return nil, err
					}
					return lsp.TextDocumentItem{
						URI:  params.TextDocument.URI,
						Text: string(b),
					}, nil
				}
			}
			msg := fmt.Sprintf("URI %s does not exist", params.TextDocument.URI)
			log.Println(msg)
			return nil, &jsonrpc2.Error{
				Code:    jsonrpc2.CodeInvalidParams,
				Message: msg,
			}
		}
		log.Println("ignoring server->client request:", req.Method)
		if req.Notif {
			return nil, nil
		}
		return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeMethodNotFound, Message: fmt.Sprintf("client handler: method not found: %q", req.Method)}
	}
}
newVFSHandler provides workspace/xfiles and textDocument/xcontent for the
zip archive ar.
package main
import (
	"archive/zip"
	"bufio"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"github.com/pkg/errors"
	"github.com/sourcegraph/go-langserver/pkg/lsp"
	"github.com/sourcegraph/jsonrpc2"
)
func usage() {
	fmt.Fprintf(flag.CommandLine.Output(), `USAGE lsp-record record|test language [testcase]
'lsp-record record language' starts a proxy on port 8081 to a docker container
for dockerfiles/language. It writes a subset of JSONRPC2 requests it receives
to the testcase. The output is intended to be used by 'lsp-record test'.
'lsp-record test language' reads in JSONRPC2 requests produced by 'lsp-record
record language' for testcase. It will send the requests to a docker container
for dockerfiles/language and write out the responses. Additionally, the
originalRootUri field in the initialize request is used to provide the file
over xcontent and xfiles requests. Note: originalRootUri field and x* requests
are Sourcegraph extensions.
If the optional 'testcase' argument is specified then input and output is read
from 'dockerfiles/language/testdata/testcase.*.json'. If not specified, then
stdin and stdout is used.
The docker containers used by lsp-record are always built and started up. This
is usually fast due to dockers caching, and makes it convenient to tweak the
Dockerfile as we try to introduce a new language. The image name is
'sgtest/codeintel-language' to avoid conflicting with the images we publish
under the sourcegraph dockerhub org.
lsp-record is not a pure JSONRPC2 transport recorder/replay utility. Instead
it is targetted to the Sourcegraph use case of testing Dockerfile we have for
languages. This allows the UI for the tool to be very minimal and simple for
the mentioned use case.
You need to configure Sourcegraph to use the proxy 'lsp-record record'
starts. For example use
  "langservers": [
    {
      "address": "tcp://localhost:8081",
      "language": "rust"
    }
  ]
To record requests to the rust language server started with
   lsp-record record rust
`)
	flag.PrintDefaults()
}
type Request struct {
	Method string           `json:"method"`
	Params *json.RawMessage `json:"params,omitempty"`
}
type Encoder struct {
	io.Writer
	enc *json.Encoder
}
func (e *Encoder) Encode(v interface{}) error {
	if e.enc == nil {
		e.enc = json.NewEncoder(e.Writer)
		e.enc.SetIndent("", "  ")
	} else {
		if _, err := e.Writer.Write([]byte{'\n'}); err != nil {
			return err
		}
	}
	return e.enc.Encode(v)
}
func writeJSONRPC2Requests(r io.Reader, w io.Writer) error {
	stream := bufio.NewReader(r)
	codec := jsonrpc2.VSCodeObjectCodec{}
	enc := &Encoder{Writer: w}
	for {
		var req Request
		if err := codec.ReadObject(stream, &req); err != nil {
			return err
		}
		if req.Method != "" {
			if err := enc.Encode(req); err != nil {
				return err
			}
		}
	}
}
func retryDial(network, address string) (net.Conn, error) {
	conn, err := net.DialTimeout(network, address, time.Second)
	for i := 0; err != nil && i < 5; i++ {
		time.Sleep(time.Second)
		conn, err = net.DialTimeout(network, address, time.Second)
	}
	return conn, err
}
func massageGitHubArchive(r *zip.ReadCloser) {
	i := 0
	for _, file := range r.File {
		if file.FileInfo().IsDir() {
			continue
		}
		file.Name = file.Name[strings.Index(file.Name, "/"):]
		r.File[i] = file
		i++
	}
	r.File = r.File[:i]
}
func fetchArchiveForRootURI(originalRootURI string) (*zip.ReadCloser, error) {
	dst := filepath.Join(os.TempDir(), "lsp-record", url.QueryEscape(originalRootURI)+".zip")
	if r, err := zip.OpenReader(dst); err == nil {
		massageGitHubArchive(r)
		return r, nil
	} else if !os.IsNotExist(err) {
		return nil, err
	}
	u, err := url.Parse(originalRootURI)
	if err != nil {
		return nil, err
	}
	if u.Host != "github.com" {
		return nil, errors.Errorf("Unsupported originalRootUri %s (only github supported)", originalRootURI)
	}
	if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
		return nil, err
	}
	repo := path.Join(u.Host, strings.TrimPrefix(u.Path, ".git"))
	rev := u.RawQuery
	url := fmt.Sprintf("https://codeload.%s/zip/%s", repo, rev)
	log.Println("fetching", url)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	dstPart := dst + ".part"
	fd, err := os.Create(dstPart)
	if err != nil {
		return nil, err
	}
	defer os.Remove(dstPart)
	_, err = io.Copy(fd, resp.Body)
	if err2 := fd.Close(); err2 != nil {
		return nil, err2
	}
	if err != nil {
		return nil, err
	}
	if err := os.Rename(dstPart, dst); err != nil {
		return nil, err
	}
	r, err := zip.OpenReader(dst)
	if err != nil {
		return nil, err
	}
	massageGitHubArchive(r)
	return r, nil
}
type jsonrpc2HandlerFunc func(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (result interface{}, err error)