| 🛟 Updated. 28d5985 k33g 17h ago | 1 | // Package profile holds everything that makes one editor built on turbo-core |
| 2 | // differ from another: what it is called, which language it is for, which |
| 3 | // language server it talks to, and the starter files it writes. |
| 4 | // |
| 5 | // It is the seam the whole library turns on. Turbo Go, Turbo Rust, Turbo |
| 6 | // Python, Turbo MoonBit, Turbo Golo and Turbo JS are the same editor with six |
| 7 | // different values of this type, so anything that would otherwise be a |
| 8 | // hardcoded "turbo-go" or "gopls" is a field here instead. |
| 9 | // |
| 10 | // The package is deliberately data and nothing else — no filesystem beyond the |
| 11 | // user's configuration directory, no processes, no colours — so that every |
| 12 | // other package in the library can depend on it without acquiring a dependency |
| 13 | // of its own. |
| 14 | // |
| 15 | // p := profile.Profile{ |
| 16 | // Name: "Turbo Go", |
| 17 | // Slug: "turbo-go", |
| 18 | // Language: "Go", |
| 19 | // } |
| 20 | // p.ProjectDir() // ".turbo-go" |
| 21 | package profile |
| 22 | |
| 23 | import ( |
| 24 | "os" |
| 25 | "path/filepath" |
| 26 | "strings" |
| 27 | ) |
| 28 | |
| 29 | // Profile is one editor's identity and the language it is built for. |
| 30 | // |
| 31 | // The zero value is not useful: Slug is what every derived path is built from, |
| 32 | // and an editor without one would write its project files into a directory |
| 33 | // called ".". |
| 34 | type Profile struct { |
| 35 | // Name is what the editor calls itself, in its window titles and its About |
| 36 | // box: "Turbo Go", "Turbo Rust". |
| 37 | Name string |
| 38 | |
| 39 | // Slug is its one-word name: "turbo-go". The binary, the project directory |
| 40 | // (with a dot in front of it), the user's own configuration directory and |
| 41 | // the environment variables that override them are all built from it. |
| 42 | Slug string |
| 43 | |
| 44 | // Language names what the editor is for, in prose meant to be read: "Go", |
| 45 | // "Rust". It appears in messages, not in file formats. |
| 46 | Language string |
| 47 | |
| 48 | // ToolsMenu is the label of the menu the project's own commands appear in, |
| 49 | // with its hot key marked: "~G~o", "Rus~t~". The plain name — what a tools |
| 50 | // file writes in its menu key — is this with the markers taken out. |
| 51 | ToolsMenu string |
| 52 | |
| 53 | // RootMarkers are the files that mark the root of a project, in the order |
| 54 | // they are looked for: "go.mod", "Cargo.toml". The language server is |
| 55 | // started in the first directory above the file being edited that holds |
| 56 | // one of them. |
| 57 | RootMarkers []string |
| 58 | |
| 59 | // Server is the language server this editor talks to. |
| 60 | Server Server |
| 61 | |
| 62 | // Templates are the starter files the editor offers to write. |
| 63 | Templates Templates |
| 64 | } |
| 65 | |
| 66 | // Server describes the language server an editor talks to, in enough detail to |
| 67 | // find it, start it, and say what to do when it is missing. |
| 68 | type Server struct { |
| 69 | // Command is the executable's name: "gopls", "rust-analyzer". |
| 70 | Command string |
| 71 | |
| 72 | // Args are the arguments it is started with. gopls wants "serve"; |
| 73 | // rust-analyzer wants none. |
| 74 | Args []string |
| 75 | |
| 76 | // InstallHint is a single command the user can copy when the server is not |
| 77 | // installed. It is shown on the status bar, so it has to fit on a line. |
| 78 | InstallHint string |
| 79 | |
| 80 | // Dirs are extra directories to look in when PATH has nothing, in the order |
| 81 | // they are tried: GOPATH/bin for Go, ~/.cargo/bin for Rust. They are |
| 82 | // resolved by the editor at start-up, because working them out means |
| 83 | // reading environment variables this package deliberately does not know |
| 84 | // about. |
| 85 | Dirs []string |
| 86 | } |
| 87 | |
| 88 | // Templates are the files the editor writes into a project's own directory |
| 89 | // when asked to, one per menu item that offers to create one. |
| 90 | // |
| 91 | // They are held here rather than compiled into the packages that write them |
| 92 | // because their contents are the one part of those files that is about a |
| 93 | // particular language: a Go project's tools are `go build ./...`, a Rust |
| 94 | // project's are `cargo build`. |
| 95 | type Templates struct { |
| 96 | // Settings is the starter settings.toml. It is formatted with two |
| 97 | // arguments, in this order: the theme name and the autosave delay, both |
| 98 | // with %q. |
| 99 | Settings string |
| 100 | |
| 101 | // Snippets is the starter snippets.toml. It is formatted with two |
| 102 | // arguments, in this order: the name of the group an ungrouped snippet |
| 103 | // falls into, and the path to the user's own snippets file, both with %s. |
| 104 | Snippets string |
| 105 | |
| 106 | // Tools is the starter tools.toml. It takes no formatting arguments. |
| 107 | Tools string |
| 108 | |
| 109 | // Agents is the starter acp.toml, listing the coding agents the editor can |
| 110 | // open a window on. It is formatted with two arguments, in this order: the |
| 111 | // editor's own project directory — ".turbo-go" — and the path to the |
| 112 | // user's own agents file, both with %s. |
| 113 | Agents string |
| 114 | } |
| 115 | |
| 116 | // ProjectDir returns the directory this editor keeps a project's own files in: |
| 117 | // its settings, its snippets, its tools. |
| 118 | // |
| 119 | // It is the slug with a dot in front, which is the convention every other tool |
| 120 | // in a repository follows. |
| 121 | // |
| 122 | // profile.Profile{Slug: "turbo-go"}.ProjectDir() // ".turbo-go" |
| 123 | func (p Profile) ProjectDir() string { return "." + p.Slug } |
| 124 | |
| 125 | // UserDir returns the directory this editor keeps the user's own files in — the |
| 126 | // ones that follow them between projects — or "" when the system cannot say |
| 127 | // where that would be. |
| 128 | // |
| 129 | // The environment variable named by DirEnvVar wins when it is set, which is |
| 130 | // what lets a test point the editor at a directory of its own instead of at the |
| 131 | // real one. |
| 132 | // |
| 133 | // profile.Profile{Slug: "turbo-go"}.UserDir() // "~/.config/turbo-go" |
| 134 | func (p Profile) UserDir() string { |
| 135 | if dir := os.Getenv(p.DirEnvVar()); dir != "" { |
| 136 | return dir |
| 137 | } |
| 138 | |
| 139 | config, err := os.UserConfigDir() |
| 140 | if err != nil { |
| 141 | return "" |
| 142 | } |
| 143 | return filepath.Join(config, p.Slug) |
| 144 | } |
| 145 | |
| 146 | // ThemeDir returns the directory the user's own themes are read from, or "" |
| 147 | // when there is nowhere to read them from. |
| 148 | // |
| 149 | // The environment variable named by ThemeDirEnvVar wins when it is set, and |
| 150 | // names the theme directory itself rather than the directory above it — a test |
| 151 | // wants to say exactly where the themes are. |
| 152 | func (p Profile) ThemeDir() string { |
| 153 | if dir := os.Getenv(p.ThemeDirEnvVar()); dir != "" { |
| 154 | return dir |
| 155 | } |
| 156 | |
| 157 | user := p.UserDir() |
| 158 | if user == "" { |
| 159 | return "" |
| 160 | } |
| 161 | return filepath.Join(user, "themes") |
| 162 | } |
| 163 | |
| 164 | // DirEnvVar names the environment variable that overrides UserDir. |
| 165 | // |
| 166 | // profile.Profile{Slug: "turbo-go"}.DirEnvVar() // "TURBO_GO_DIR" |
| 167 | func (p Profile) DirEnvVar() string { return p.envPrefix() + "_DIR" } |
| 168 | |
| 169 | // ThemeDirEnvVar names the environment variable that overrides ThemeDir. |
| 170 | // |
| 171 | // profile.Profile{Slug: "turbo-go"}.ThemeDirEnvVar() // "TURBO_GO_THEME_DIR" |
| 172 | func (p Profile) ThemeDirEnvVar() string { return p.envPrefix() + "_THEME_DIR" } |
| 173 | |
| 174 | // SnippetDirEnvVar names the environment variable that overrides where the |
| 175 | // user's own snippets are read from. |
| 176 | // |
| 177 | // profile.Profile{Slug: "turbo-go"}.SnippetDirEnvVar() // "TURBO_GO_SNIPPET_DIR" |
| 178 | func (p Profile) SnippetDirEnvVar() string { return p.envPrefix() + "_SNIPPET_DIR" } |
| 179 | |
| 180 | // envPrefix turns the slug into the shape an environment variable takes: |
| 181 | // upper case, with dashes as underscores. |
| 182 | func (p Profile) envPrefix() string { |
| 183 | return strings.ToUpper(strings.ReplaceAll(p.Slug, "-", "_")) |
| 184 | } |