func (e *Editor) GoToTypeDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
	if err := e.checkBufferPosition(path, pos); err != nil {
		return "", Pos{}, err
	}
	params := &protocol.TypeDefinitionParams{}
	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
	params.Position = pos.ToProtocolPosition()
	resp, err := e.Server.TypeDefinition(ctx, params)
	if err != nil {
		return "", Pos{}, errors.Errorf("type definition: %w", err)
	}
	return e.extractFirstPathAndPos(ctx, resp)
}
GoToTypeDefinition jumps to the type definition of the symbol at the given position
in an open buffer.
package fake
import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/jsonrpc2"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/command"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/protocol"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/span"
	errors "golang.org/x/xerrors"
)
type Editor struct {
	Config EditorConfig
	Server     protocol.Server
	serverConn jsonrpc2.Conn
	client     *Client
	sandbox    *Sandbox
	defaultEnv map[string]string
	mu sync.Mutex
	buffers map[string]buffer
	serverCapabilities protocol.ServerCapabilities
	callsMu sync.Mutex
	calls   CallCounts
}
type CallCounts struct {
	DidOpen, DidChange, DidSave, DidChangeWatchedFiles, DidClose uint64
}
type buffer struct {
	windowsLineEndings bool
	version            int
	path               string
	lines              []string
	dirty              bool
}
func (b buffer) text() string {
	eol := "\n"
	if b.windowsLineEndings {
		eol = "\r\n"
	}
	return strings.Join(b.lines, eol)
}
type EditorConfig struct {
	Env        map[string]string
	BuildFlags []string
	CodeLenses map[string]bool
	SymbolMatcher, SymbolStyle *string
	LimitWorkspaceScope bool
	WorkspaceFolders []string
	EnableStaticcheck bool
	AllExperiments bool
	SendPID bool
	WindowsLineEndings bool
	ImportShortcut                 string
	DirectoryFilters               []string
	VerboseOutput                  bool
	ExperimentalUseInvalidMetadata bool
}
func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
	return &Editor{
		buffers:    make(map[string]buffer),
		sandbox:    sandbox,
		defaultEnv: sandbox.GoEnv(),
		Config:     config,
	}
}
func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) {
	e.serverConn = conn
	e.Server = protocol.ServerDispatcher(conn)
	e.client = &Client{editor: e, hooks: hooks}
	conn.Go(ctx,
		protocol.Handlers(
			protocol.ClientHandler(e.client,
				jsonrpc2.MethodNotFound)))
	if err := e.initialize(ctx, e.Config.WorkspaceFolders); err != nil {
		return nil, err
	}
	e.sandbox.Workdir.AddWatcher(e.onFileChanges)
	return e, nil
}
func (e *Editor) Stats() CallCounts {
	e.callsMu.Lock()
	defer e.callsMu.Unlock()
	return e.calls
}
func (e *Editor) Shutdown(ctx context.Context) error {
	if e.Server != nil {
		if err := e.Server.Shutdown(ctx); err != nil {
			return errors.Errorf("Shutdown: %w", err)
		}
	}
	return nil
}
func (e *Editor) Exit(ctx context.Context) error {
	if e.Server != nil {
		if err := e.Server.Exit(ctx); err != nil {
			return errors.Errorf("Exit: %w", err)
		}
	}
	return nil
}
func (e *Editor) Close(ctx context.Context) error {
	if err := e.Shutdown(ctx); err != nil {
		return err
	}
	if err := e.Exit(ctx); err != nil {
		return err
	}
	select {
	case <-e.serverConn.Done():
		return nil
	case <-ctx.Done():
		return errors.Errorf("connection not closed: %w", ctx.Err())
	}
}
func (e *Editor) Client() *Client {
	return e.client
}
func (e *Editor) overlayEnv() map[string]string {
	env := make(map[string]string)
	for k, v := range e.defaultEnv {
		v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
		env[k] = v
	}
	for k, v := range e.Config.Env {
		v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
		env[k] = v
	}
	return env
}
func (e *Editor) configuration() map[string]interface{} {
	config := map[string]interface{}{
		"verboseWorkDoneProgress": true,
		"env":                     e.overlayEnv(),
		"expandWorkspaceToModule": !e.Config.LimitWorkspaceScope,
		"completionBudget":        "10s",
	}
	if e.Config.BuildFlags != nil {
		config["buildFlags"] = e.Config.BuildFlags
	}
	if e.Config.DirectoryFilters != nil {
		config["directoryFilters"] = e.Config.DirectoryFilters
	}
	if e.Config.ExperimentalUseInvalidMetadata {
		config["experimentalUseInvalidMetadata"] = true
	}
	if e.Config.CodeLenses != nil {
		config["codelenses"] = e.Config.CodeLenses
	}
	if e.Config.SymbolMatcher != nil {
		config["symbolMatcher"] = *e.Config.SymbolMatcher
	}
	if e.Config.SymbolStyle != nil {
		config["symbolStyle"] = *e.Config.SymbolStyle
	}
	if e.Config.EnableStaticcheck {
		config["staticcheck"] = true
	}
	if e.Config.AllExperiments {
		config["allExperiments"] = true
	}
	if e.Config.VerboseOutput {
		config["verboseOutput"] = true
	}
	if e.Config.ImportShortcut != "" {
		config["importShortcut"] = e.Config.ImportShortcut
	}
	config["diagnosticsDelay"] = "10ms"
	return config
}
func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) error {
	params := &protocol.ParamInitialize{}
	params.ClientInfo.Name = "fakeclient"
	params.ClientInfo.Version = "v1.0.0"
	if workspaceFolders == nil {
		workspaceFolders = []string{string(e.sandbox.Workdir.RelativeTo)}
	}
	for _, folder := range workspaceFolders {
		params.WorkspaceFolders = append(params.WorkspaceFolders, protocol.WorkspaceFolder{
			URI:  string(e.sandbox.Workdir.URI(folder)),
			Name: filepath.Base(folder),
		})
	}
	params.Capabilities.Workspace.Configuration = true
	params.Capabilities.Window.WorkDoneProgress = true
	params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
	params.InitializationOptions = e.configuration()
	if e.Config.SendPID {
		params.ProcessID = int32(os.Getpid())
	}
	params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
	params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true
	params.Trace = "messages"
	if e.Server != nil {
		resp, err := e.Server.Initialize(ctx, params)
		if err != nil {
			return errors.Errorf("initialize: %w", err)
		}
		e.mu.Lock()
		e.serverCapabilities = resp.Capabilities
		e.mu.Unlock()
		if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
			return errors.Errorf("initialized: %w", err)
		}
	}
	return nil
}
func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
	if e.Server == nil {
		return
	}
	e.callsMu.Lock()
	e.calls.DidChangeWatchedFiles++
	e.callsMu.Unlock()
	go func() {
		e.mu.Lock()
		defer e.mu.Unlock()
		var lspevts []protocol.FileEvent
		for _, evt := range evts {
			lspevts = append(lspevts, evt.ProtocolEvent)
			if buf, ok := e.buffers[evt.Path]; ok {
				if buf.dirty || evt.ProtocolEvent.Type == protocol.Deleted {
					continue
				}
				content, err := e.sandbox.Workdir.ReadFile(evt.Path)
				if err != nil {
					continue  
				}
				if content == buf.text() {
					continue
				}
				_ = e.setBufferContentLocked(ctx, evt.Path, false, lines(content), nil)
			}
		}
		e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
			Changes: lspevts,
		})
	}()
}
func (e *Editor) OpenFile(ctx context.Context, path string) error {
	content, err := e.sandbox.Workdir.ReadFile(path)
	if err != nil {
		return err
	}
	return e.createBuffer(ctx, path, false, content)
}
func textDocumentItem(wd *Workdir, buf buffer) protocol.TextDocumentItem {
	uri := wd.URI(buf.path)
	languageID := ""
	if strings.HasSuffix(buf.path, ".go") {
		languageID = "go"
	}
	return protocol.TextDocumentItem{
		URI:        uri,
		LanguageID: languageID,
		Version:    int32(buf.version),
		Text:       buf.text(),
	}
}
func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
	return e.createBuffer(ctx, path, true, content)
}
func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
	buf := buffer{
		windowsLineEndings: e.Config.WindowsLineEndings,
		version:            1,
		path:               path,
		lines:              lines(content),
		dirty:              dirty,
	}
	e.mu.Lock()
	defer e.mu.Unlock()
	e.buffers[path] = buf
	item := textDocumentItem(e.sandbox.Workdir, buf)
	if e.Server != nil {
		if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
			TextDocument: item,
		}); err != nil {
			return errors.Errorf("DidOpen: %w", err)
		}
		e.callsMu.Lock()
		e.calls.DidOpen++
		e.callsMu.Unlock()
	}
	return nil
}
func lines(content string) []string {
	lines := strings.Split(content, "\n")
	for i, l := range lines {
		lines[i] = strings.TrimSuffix(l, "\r")
	}
	return lines
}
func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
	e.mu.Lock()
	_, ok := e.buffers[path]
	if !ok {
		e.mu.Unlock()
		return ErrUnknownBuffer
	}
	delete(e.buffers, path)
	e.mu.Unlock()
	if e.Server != nil {
		if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
			TextDocument: e.textDocumentIdentifier(path),
		}); err != nil {
			return errors.Errorf("DidClose: %w", err)
		}
		e.callsMu.Lock()
		e.calls.DidClose++
		e.callsMu.Unlock()
	}
	return nil
}
func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier {
	return protocol.TextDocumentIdentifier{
		URI: e.sandbox.Workdir.URI(path),
	}
}
func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
	if err := e.OrganizeImports(ctx, path); err != nil {
		return errors.Errorf("organizing imports before save: %w", err)
	}
	if err := e.FormatBuffer(ctx, path); err != nil {
		return errors.Errorf("formatting before save: %w", err)
	}
	return e.SaveBufferWithoutActions(ctx, path)
}
func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	buf, ok := e.buffers[path]
	if !ok {
		return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path))
	}
	content := buf.text()
	includeText := false
	syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
	if ok {
		includeText = syncOptions.Save.IncludeText
	}
	docID := e.textDocumentIdentifier(buf.path)
	if e.Server != nil {
		if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
			TextDocument: docID,
			Reason:       protocol.Manual,
		}); err != nil {
			return errors.Errorf("WillSave: %w", err)
		}
	}
	if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
		return errors.Errorf("writing %q: %w", path, err)
	}
	buf.dirty = false
	e.buffers[path] = buf
	if e.Server != nil {
		params := &protocol.DidSaveTextDocumentParams{
			TextDocument: docID,
		}
		if includeText {
			params.Text = &content
		}
		if err := e.Server.DidSave(ctx, params); err != nil {
			return errors.Errorf("DidSave: %w", err)
		}
		e.callsMu.Lock()
		e.calls.DidSave++
		e.callsMu.Unlock()
	}
	return nil
}
func contentPosition(content string, offset int) (Pos, error) {
	scanner := bufio.NewScanner(strings.NewReader(content))
	start := 0
	line := 0
	for scanner.Scan() {
		end := start + len([]rune(scanner.Text())) + 1
		if offset < end {
			return Pos{Line: line, Column: offset - start}, nil
		}
		start = end
		line++
	}
	if err := scanner.Err(); err != nil {
		return Pos{}, errors.Errorf("scanning content: %w", err)
	}
	if (strings.HasSuffix(content, "\n") || content == "") && offset == start {
		return Pos{Line: line, Column: 0}, nil
	}
	return Pos{}, fmt.Errorf("position %d out of bounds in %q (line = %d, start = %d)", offset, content, line, start)
}
var (
	ErrNoMatch       = errors.New("no match")
	ErrUnknownBuffer = errors.New("unknown buffer")
)
func regexpRange(content, re string) (Pos, Pos, error) {
	content = normalizeEOL(content)
	var start, end int
	rec, err := regexp.Compile(re)
	if err != nil {
		return Pos{}, Pos{}, err
	}
	indexes := rec.FindStringSubmatchIndex(content)
	if indexes == nil {
		return Pos{}, Pos{}, ErrNoMatch
	}
	switch len(indexes) {
	case 2:
		start, end = indexes[0], indexes[1]
	case 4:
		start, end = indexes[2], indexes[3]
	default:
		return Pos{}, Pos{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
	}
	startPos, err := contentPosition(content, start)
	if err != nil {
		return Pos{}, Pos{}, err
	}
	endPos, err := contentPosition(content, end)
	if err != nil {
		return Pos{}, Pos{}, err
	}
	return startPos, endPos, nil
}
func normalizeEOL(content string) string {
	return strings.Join(lines(content), "\n")
}
func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) {
	e.mu.Lock()
	defer e.mu.Unlock()
	buf, ok := e.buffers[bufName]
	if !ok {
		return Pos{}, Pos{}, ErrUnknownBuffer
	}
	return regexpRange(buf.text(), re)
}
func (e *Editor) RegexpSearch(bufName, re string) (Pos, error) {
	start, _, err := e.RegexpRange(bufName, re)
	return start, err
}
func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	buf, ok := e.buffers[path]
	if !ok {
		return ErrUnknownBuffer
	}
	content := buf.text()
	start, end, err := regexpRange(content, re)
	if err != nil {
		return err
	}
	return e.editBufferLocked(ctx, path, []Edit{{
		Start: start,
		End:   end,
		Text:  replace,
	}})
}
func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.editBufferLocked(ctx, path, edits)
}
func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	lines := lines(content)
	return e.setBufferContentLocked(ctx, path, true, lines, nil)
}
func (e *Editor) HasBuffer(name string) bool {
	e.mu.Lock()
	defer e.mu.Unlock()
	_, ok := e.buffers[name]
	return ok
}
func (e *Editor) BufferText(name string) string {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.buffers[name].text()
}
func (e *Editor) BufferVersion(name string) int {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.buffers[name].version
}
func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []Edit) error {
	buf, ok := e.buffers[path]
	if !ok {
		return fmt.Errorf("unknown buffer %q", path)
	}
	content := make([]string, len(buf.lines))
	copy(content, buf.lines)
	content, err := editContent(content, edits)
	if err != nil {
		return err
	}
	return e.setBufferContentLocked(ctx, path, true, content, edits)
}
func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []string, fromEdits []Edit) error {
	buf, ok := e.buffers[path]
	if !ok {
		return fmt.Errorf("unknown buffer %q", path)
	}
	buf.lines = content
	buf.version++
	buf.dirty = dirty
	e.buffers[path] = buf
	var evts []protocol.TextDocumentContentChangeEvent
	if len(fromEdits) == 1 {
		evts = append(evts, fromEdits[0].toProtocolChangeEvent())
	} else {
		evts = append(evts, protocol.TextDocumentContentChangeEvent{
			Text: buf.text(),
		})
	}
	params := &protocol.DidChangeTextDocumentParams{
		TextDocument: protocol.VersionedTextDocumentIdentifier{
			Version:                int32(buf.version),
			TextDocumentIdentifier: e.textDocumentIdentifier(buf.path),
		},
		ContentChanges: evts,
	}
	if e.Server != nil {
		if err := e.Server.DidChange(ctx, params); err != nil {
			return errors.Errorf("DidChange: %w", err)
		}
		e.callsMu.Lock()
		e.calls.DidChange++
		e.callsMu.Unlock()
	}
	return nil
}
func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
	if err := e.checkBufferPosition(path, pos); err != nil {
		return "", Pos{}, err
	}
	params := &protocol.DefinitionParams{}
	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
	params.Position = pos.ToProtocolPosition()
	resp, err := e.Server.Definition(ctx, params)
	if err != nil {
		return "", Pos{}, errors.Errorf("definition: %w", err)
	}
	return e.extractFirstPathAndPos(ctx, resp)
}