Vectrum
Sign In
44 lines · 1.3 KB · Go
package diag

import (
	"os"
	"path/filepath"
)

// workspaceRoot is the directory a language server should be told is the
// workspace for path: the nearest ancestor holding one of markers — go.mod,
// Cargo.toml, package.json — and failing that the nearest .git, and failing
// that the file's own directory.
//
// The walk is not bounded by the agent's working root, deliberately. The
// working root is where the model is allowed to write; the workspace is what
// the compiler needs to see, and the two differ whenever the user points Codey
// at one package of a larger module. Handing gopls a subdirectory of the module
// gets a server that cannot resolve a single import.
func workspaceRoot(path string, markers []string) string {
	dir := filepath.Dir(path)
	if root := ancestorWith(dir, markers); root != "" {
		return root
	}
	if root := ancestorWith(dir, []string{".git"}); root != "" {
		return root
	}
	return dir
}

// ancestorWith walks from dir to the filesystem root and returns the first
// directory containing any of names, or "".
func ancestorWith(dir string, names []string) string {
	for {
		for _, n := range names {
			if _, err := os.Stat(filepath.Join(dir, n)); err == nil {
				return dir
			}
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			return ""
		}
		dir = parent
	}
}