// Package profile holds everything that makes one editor built on turbo-core // differ from another: what it is called, which language it is for, which // language server it talks to, and the starter files it writes. // // It is the seam the whole library turns on. Turbo Go, Turbo Rust, Turbo // Python, Turbo MoonBit, Turbo Golo and Turbo JS are the same editor with six // different values of this type, so anything that would otherwise be a // hardcoded "turbo-go" or "gopls" is a field here instead. // // The package is deliberately data and nothing else — no filesystem beyond the // user's configuration directory, no processes, no colours — so that every // other package in the library can depend on it without acquiring a dependency // of its own. // // p := profile.Profile{ // Name: "Turbo Go", // Slug: "turbo-go", // Language: "Go", // } // p.ProjectDir() // ".turbo-go" package profile import ( "os" "path/filepath" "strings" ) // Profile is one editor's identity and the language it is built for. // // The zero value is not useful: Slug is what every derived path is built from, // and an editor without one would write its project files into a directory // called ".". type Profile struct { // Name is what the editor calls itself, in its window titles and its About // box: "Turbo Go", "Turbo Rust". Name string // Slug is its one-word name: "turbo-go". The binary, the project directory // (with a dot in front of it), the user's own configuration directory and // the environment variables that override them are all built from it. Slug string // Language names what the editor is for, in prose meant to be read: "Go", // "Rust". It appears in messages, not in file formats. Language string // ToolsMenu is the label of the menu the project's own commands appear in, // with its hot key marked: "~G~o", "Rus~t~". The plain name — what a tools // file writes in its menu key — is this with the markers taken out. ToolsMenu string // RootMarkers are the files that mark the root of a project, in the order // they are looked for: "go.mod", "Cargo.toml". The language server is // started in the first directory above the file being edited that holds // one of them. RootMarkers []string // Server is the language server this editor talks to. Server Server // Templates are the starter files the editor offers to write. Templates Templates } // Server describes the language server an editor talks to, in enough detail to // find it, start it, and say what to do when it is missing. type Server struct { // Command is the executable's name: "gopls", "rust-analyzer". Command string // Args are the arguments it is started with. gopls wants "serve"; // rust-analyzer wants none. Args []string // InstallHint is a single command the user can copy when the server is not // installed. It is shown on the status bar, so it has to fit on a line. InstallHint string // Dirs are extra directories to look in when PATH has nothing, in the order // they are tried: GOPATH/bin for Go, ~/.cargo/bin for Rust. They are // resolved by the editor at start-up, because working them out means // reading environment variables this package deliberately does not know // about. Dirs []string } // Templates are the files the editor writes into a project's own directory // when asked to, one per menu item that offers to create one. // // They are held here rather than compiled into the packages that write them // because their contents are the one part of those files that is about a // particular language: a Go project's tools are `go build ./...`, a Rust // project's are `cargo build`. type Templates struct { // Settings is the starter settings.toml. It is formatted with two // arguments, in this order: the theme name and the autosave delay, both // with %q. Settings string // Snippets is the starter snippets.toml. It is formatted with two // arguments, in this order: the name of the group an ungrouped snippet // falls into, and the path to the user's own snippets file, both with %s. Snippets string // Tools is the starter tools.toml. It takes no formatting arguments. Tools string // Agents is the starter acp.toml, listing the coding agents the editor can // open a window on. It is formatted with two arguments, in this order: the // editor's own project directory — ".turbo-go" — and the path to the // user's own agents file, both with %s. Agents string } // ProjectDir returns the directory this editor keeps a project's own files in: // its settings, its snippets, its tools. // // It is the slug with a dot in front, which is the convention every other tool // in a repository follows. // // profile.Profile{Slug: "turbo-go"}.ProjectDir() // ".turbo-go" func (p Profile) ProjectDir() string { return "." + p.Slug } // UserDir returns the directory this editor keeps the user's own files in — the // ones that follow them between projects — or "" when the system cannot say // where that would be. // // The environment variable named by DirEnvVar wins when it is set, which is // what lets a test point the editor at a directory of its own instead of at the // real one. // // profile.Profile{Slug: "turbo-go"}.UserDir() // "~/.config/turbo-go" func (p Profile) UserDir() string { if dir := os.Getenv(p.DirEnvVar()); dir != "" { return dir } config, err := os.UserConfigDir() if err != nil { return "" } return filepath.Join(config, p.Slug) } // ThemeDir returns the directory the user's own themes are read from, or "" // when there is nowhere to read them from. // // The environment variable named by ThemeDirEnvVar wins when it is set, and // names the theme directory itself rather than the directory above it — a test // wants to say exactly where the themes are. func (p Profile) ThemeDir() string { if dir := os.Getenv(p.ThemeDirEnvVar()); dir != "" { return dir } user := p.UserDir() if user == "" { return "" } return filepath.Join(user, "themes") } // DirEnvVar names the environment variable that overrides UserDir. // // profile.Profile{Slug: "turbo-go"}.DirEnvVar() // "TURBO_GO_DIR" func (p Profile) DirEnvVar() string { return p.envPrefix() + "_DIR" } // ThemeDirEnvVar names the environment variable that overrides ThemeDir. // // profile.Profile{Slug: "turbo-go"}.ThemeDirEnvVar() // "TURBO_GO_THEME_DIR" func (p Profile) ThemeDirEnvVar() string { return p.envPrefix() + "_THEME_DIR" } // SnippetDirEnvVar names the environment variable that overrides where the // user's own snippets are read from. // // profile.Profile{Slug: "turbo-go"}.SnippetDirEnvVar() // "TURBO_GO_SNIPPET_DIR" func (p Profile) SnippetDirEnvVar() string { return p.envPrefix() + "_SNIPPET_DIR" } // envPrefix turns the slug into the shape an environment variable takes: // upper case, with dashes as underscores. func (p Profile) envPrefix() string { return strings.ToUpper(strings.ReplaceAll(p.Slug, "-", "_")) }