func fileExists(ctx context.Context, uri span.URI, source source.FileSource) (bool, error) {
	fh, err := source.GetFile(ctx, uri)
	if err != nil {
		return false, err
	}
	return fileHandleExists(fh)
}
fileExists reports if the file uri exists within source.
package cache
import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"golang.org/x/mod/modfile"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/event"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/lsp/source"
	workfile "github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/mod/modfile"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/span"
	"github.com/govim/govim/cmd/govim/internal/golang_org_x_tools/xcontext"
	errors "golang.org/x/xerrors"
)
type workspaceSource int
const (
	legacyWorkspace = iota
	goplsModWorkspace
	goWorkWorkspace
	fileSystemWorkspace
)
func (s workspaceSource) String() string {
	switch s {
	case legacyWorkspace:
		return "legacy"
	case goplsModWorkspace:
		return "gopls.mod"
	case goWorkWorkspace:
		return "go.work"
	case fileSystemWorkspace:
		return "file system"
	default:
		return "!(unknown module source)"
	}
}
type workspace struct {
	root         span.URI
	excludePath  func(string) bool
	moduleSource workspaceSource
	activeModFiles map[span.URI]struct{}
	knownModFiles map[span.URI]struct{}
	go111moduleOff bool
	buildMu  sync.Mutex
	built    bool
	buildErr error
	mod      *modfile.File
	sum      []byte
	wsDirs   map[span.URI]struct{}
}
func newWorkspace(ctx context.Context, root span.URI, fs source.FileSource, excludePath func(string) bool, go111moduleOff bool, experimental bool) (*workspace, error) {
	ws, err := parseExplicitWorkspaceFile(ctx, root, fs, excludePath)
	if err == nil {
		return ws, nil
	}
	knownModFiles, err := findModules(root, excludePath, 0)
	if err != nil {
		return nil, err
	}
	if go111moduleOff {
		return &workspace{
			root:           root,
			excludePath:    excludePath,
			moduleSource:   legacyWorkspace,
			knownModFiles:  knownModFiles,
			go111moduleOff: true,
		}, nil
	}
	if !experimental {
		activeModFiles, err := getLegacyModules(ctx, root, fs)
		if err != nil {
			return nil, err
		}
		return &workspace{
			root:           root,
			excludePath:    excludePath,
			activeModFiles: activeModFiles,
			knownModFiles:  knownModFiles,
			moduleSource:   legacyWorkspace,
		}, nil
	}
	return &workspace{
		root:           root,
		excludePath:    excludePath,
		activeModFiles: knownModFiles,
		knownModFiles:  knownModFiles,
		moduleSource:   fileSystemWorkspace,
	}, nil
}
func parseExplicitWorkspaceFile(ctx context.Context, root span.URI, fs source.FileSource, excludePath func(string) bool) (*workspace, error) {
	for _, src := range []workspaceSource{goWorkWorkspace, goplsModWorkspace} {
		fh, err := fs.GetFile(ctx, uriForSource(root, src))
		if err != nil {
			return nil, err
		}
		contents, err := fh.Read()
		if err != nil {
			continue
		}
		var file *modfile.File
		var activeModFiles map[span.URI]struct{}
		switch src {
		case goWorkWorkspace:
			file, activeModFiles, err = parseGoWork(ctx, root, fh.URI(), contents, fs)
		case goplsModWorkspace:
			file, activeModFiles, err = parseGoplsMod(root, fh.URI(), contents)
		}
		if err != nil {
			return nil, err
		}
		return &workspace{
			root:           root,
			excludePath:    excludePath,
			activeModFiles: activeModFiles,
			knownModFiles:  activeModFiles,
			mod:            file,
			moduleSource:   src,
		}, nil
	}
	return nil, noHardcodedWorkspace
}
var noHardcodedWorkspace = errors.New("no hardcoded workspace")
func (w *workspace) getKnownModFiles() map[span.URI]struct{} {
	return w.knownModFiles
}
func (w *workspace) getActiveModFiles() map[span.URI]struct{} {
	return w.activeModFiles
}
func (w *workspace) modFile(ctx context.Context, fs source.FileSource) (*modfile.File, error) {
	w.build(ctx, fs)
	return w.mod, w.buildErr
}
func (w *workspace) sumFile(ctx context.Context, fs source.FileSource) ([]byte, error) {
	w.build(ctx, fs)
	return w.sum, w.buildErr
}
func (w *workspace) build(ctx context.Context, fs source.FileSource) {
	w.buildMu.Lock()
	defer w.buildMu.Unlock()
	if w.built {
		return
	}
	ctx = xcontext.Detach(ctx)
	if w.moduleSource != goplsModWorkspace {
		file, err := buildWorkspaceModFile(ctx, w.activeModFiles, fs)
		switch {
		case err == nil:
			w.mod = file
		case w.mod != nil:
			event.Error(ctx, "building workspace mod file", err)
		default:
			w.buildErr = err
		}
	}
	if w.mod != nil {
		w.wsDirs = map[span.URI]struct{}{
			w.root: {},
		}
		for _, r := range w.mod.Replace {
			if r.New.Version != "" {
				continue
			}
			w.wsDirs[span.URIFromPath(r.New.Path)] = struct{}{}
		}
	}
	if len(w.wsDirs) == 0 {
		w.wsDirs = map[span.URI]struct{}{
			w.root: {},
		}
	}
	sum, err := buildWorkspaceSumFile(ctx, w.activeModFiles, fs)
	if err == nil {
		w.sum = sum
	} else {
		event.Error(ctx, "building workspace sum file", err)
	}
	w.built = true
}
func (w *workspace) dirs(ctx context.Context, fs source.FileSource) []span.URI {
	w.build(ctx, fs)
	var dirs []span.URI
	for d := range w.wsDirs {
		dirs = append(dirs, d)
	}
	sort.Slice(dirs, func(i, j int) bool {
		return source.CompareURI(dirs[i], dirs[j]) < 0
	})
	return dirs
}
func (w *workspace) invalidate(ctx context.Context, changes map[span.URI]*fileChange, fs source.FileSource) (_ *workspace, changed, reload bool) {
	w.buildMu.Lock()
	defer w.buildMu.Unlock()
	result := &workspace{
		root:           w.root,
		moduleSource:   w.moduleSource,
		knownModFiles:  make(map[span.URI]struct{}),
		activeModFiles: make(map[span.URI]struct{}),
		go111moduleOff: w.go111moduleOff,
		mod:            w.mod,
		sum:            w.sum,
		wsDirs:         w.wsDirs,
	}
	for k, v := range w.knownModFiles {
		result.knownModFiles[k] = v
	}
	for k, v := range w.activeModFiles {
		result.activeModFiles[k] = v
	}
	if changedInner, reloadInner, found := updateExplicitWorkspaceFile(ctx, w, result, changes, fs); found {
		changed = changedInner
		reload = reloadInner
	}
	if result.moduleSource != goplsModWorkspace && result.moduleSource != goWorkWorkspace {
		for uri, change := range changes {
			if change.isUnchanged || !isGoMod(uri) || !source.InDir(result.root.Filename(), uri.Filename()) {
				continue
			}
			changed = true
			active := result.moduleSource != legacyWorkspace || source.CompareURI(modURI(w.root), uri) == 0
			reload = reload || (active && change.fileHandle.Saved())
			if change.exists {
				result.knownModFiles[uri] = struct{}{}
				if active {
					result.activeModFiles[uri] = struct{}{}
				}
			} else {
				delete(result.knownModFiles, uri)
				delete(result.activeModFiles, uri)
			}
		}
	}
	for uri, change := range changes {
		if !isGoSum(uri) {
			continue
		}
		dir := filepath.Dir(uri.Filename())
		modURI := span.URIFromPath(filepath.Join(dir, "go.mod"))
		if _, active := result.activeModFiles[modURI]; !active {
			continue
		}
		changed = true
		reload = reload || change.fileHandle.Saved()
	}
	if !changed {
		return w, false, false
	}
	return result, changed, reload
}
func updateExplicitWorkspaceFile(ctx context.Context, w, result *workspace, changes map[span.URI]*fileChange, fs source.FileSource) (changed, reload, found bool) {
	for _, src := range []workspaceSource{goplsModWorkspace, goWorkWorkspace} {
		uri := uriForSource(w.root, src)
		change, ok := changes[uri]
		if !ok || change.isUnchanged {
			continue
		}
		found = true
		if change.exists {
			var parsedFile *modfile.File
			var parsedModules map[span.URI]struct{}
			var err error
			switch src {
			case goWorkWorkspace:
				parsedFile, parsedModules, err = parseGoWork(ctx, w.root, uri, change.content, fs)
			case goplsModWorkspace:
				parsedFile, parsedModules, err = parseGoplsMod(w.root, uri, change.content)
			}
			if err != nil {
				event.Error(ctx, fmt.Sprintf("parsing %s", filepath.Base(uri.Filename())), err)
			}
			changed = true
			reload = change.fileHandle.Saved()
			result.mod = parsedFile
			result.moduleSource = src
			result.knownModFiles = parsedModules
			result.activeModFiles = make(map[span.URI]struct{})
			for k, v := range parsedModules {
				result.activeModFiles[k] = v
			}
		} else {
			changed = true
			reload = true
			result.moduleSource = fileSystemWorkspace
			result.mod = nil
			knownModFiles, err := findModules(w.root, w.excludePath, 0)
			if err != nil {
				result.knownModFiles = nil
				result.activeModFiles = nil
				event.Error(ctx, "finding file system modules", err)
			} else {
				result.knownModFiles = knownModFiles
				result.activeModFiles = make(map[span.URI]struct{})
				for k, v := range result.knownModFiles {
					result.activeModFiles[k] = v
				}
			}
		}
	}
	return changed, reload, found
}
func uriForSource(root span.URI, src workspaceSource) span.URI {
	var basename string
	switch src {
	case goplsModWorkspace:
		basename = "gopls.mod"
	case goWorkWorkspace:
		basename = "go.work"
	default:
		return ""
	}
	return span.URIFromPath(filepath.Join(root.Filename(), basename))
}
func modURI(root span.URI) span.URI {
	return span.URIFromPath(filepath.Join(root.Filename(), "go.mod"))
}
func isGoMod(uri span.URI) bool {
	return filepath.Base(uri.Filename()) == "go.mod"
}
func isGoSum(uri span.URI) bool {
	return filepath.Base(uri.Filename()) == "go.sum"
}