| 📦 Turbo Rust 713ea5c k33g 10h ago | 1 | // Package rustlang is everything about Turbo Rust that is about *Rust*: how the |
| 2 | // editor names itself, which language server it talks to, what a Cargo |
| 3 | // project's starter files say, and how Rust source is coloured. |
| 4 | // |
| 5 | // Everything else the editor does lives in turbo-core, which knows nothing |
| 6 | // about Rust. This package is the whole of the difference between Turbo Rust |
| 7 | // and Turbo Go, which is what makes a third editor a matter of writing one of |
| 8 | // these rather than forking anything. |
| 9 | // |
| 10 | // rustlang.Register() // teach the library to colour Rust |
| 11 | // editor := app.New(screen, name, rustlang.Profile()) |
| 12 | package rustlang |
| 13 | |
| 14 | import ( |
| 15 | "os" |
| 16 | "path/filepath" |
| 17 | |
| 18 | "rickub.com/turbo-editors/turbo-core/profile" |
| 19 | "rickub.com/turbo-editors/turbo-core/syntax" |
| 20 | ) |
| 21 | |
| 22 | // Name and Slug are what the editor calls itself. The slug is also its binary, |
| 23 | // its project directory (as .turbo-rust) and the stem of its environment |
| 24 | // variables (as TURBO_RUST_…), so it is not free to change. |
| 25 | const ( |
| 26 | Name = "Turbo Rust" |
| 27 | Slug = "turbo-rust" |
| 28 | ) |
| 29 | |
| 30 | // Language is the name Rust is known by: the value LanguageOf returns for a .rs |
| 31 | // file, and what a snippets file writes in its languages key. |
| 32 | const Language syntax.Language = "rust" |
| 33 | |
| 34 | // ServerCommand is the language server Turbo Rust talks to, and InstallHint the |
| 35 | // single command that installs it. |
| 36 | // |
| 37 | // rustup is the way nearly everybody has Rust, and `rustup component add` is |
| 38 | // the shortest true answer for them. Somebody who installed Rust from their |
| 39 | // distribution will have their own package for it; the hint has to fit on a |
| 40 | // status bar, so it names the common case. |
| 41 | const ( |
| 42 | ServerCommand = "rust-analyzer" |
| 43 | InstallHint = "rustup component add rust-analyzer" |
| 44 | ) |
| 45 | |
| 46 | // Profile returns the editor Turbo Rust is. |
| 47 | // |
| 48 | // It is a function rather than a variable because Server.Dirs is worked out |
| 49 | // from the environment, and a variable would freeze whatever CARGO_HOME said |
| 50 | // when the package was linked. |
| 51 | func Profile() profile.Profile { |
| 52 | return profile.Profile{ |
| 53 | Name: Name, |
| 54 | Slug: Slug, |
| 55 | Language: "Rust", |
| 56 | // R is Run's and S is Search's, so the hot key lands on the T. The |
| 57 | // alternative was naming the menu Cargo, which would have taken C — but |
| 58 | // the menu holds whatever the project put in its tools file, and that is |
| 59 | // not always cargo. |
| 60 | ToolsMenu: "Rus~t~", |
| 61 | // Cargo.toml is the crate's boundary, and the crate is what |
| 62 | // rust-analyzer loads. A workspace root has one too, and the nearest one |
| 63 | // going up is the right answer for both. |
| 64 | RootMarkers: []string{"Cargo.toml"}, |
| 65 | Server: profile.Server{ |
| 66 | Command: ServerCommand, |
| 67 | // rust-analyzer takes no subcommand, unlike gopls. |
| 68 | Args: nil, |
| 69 | InstallHint: InstallHint, |
| 70 | Dirs: []string{CargoBinDir(), RustupToolchainBinDir()}, |
| 71 | }, |
| 72 | Templates: profile.Templates{ |
| 73 | Settings: settingsTemplate, |
| 74 | Snippets: snippetsTemplate, |
| 75 | Tools: toolsTemplate, |
| 76 | Agents: agentsTemplate, |
| 77 | }, |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | // Register teaches turbo-core to colour Rust. |
| 82 | // |
| 83 | // It is called explicitly at start-up rather than from an init function so that |
| 84 | // "which languages does this editor know?" is answered by reading main, not by |
| 85 | // working out which packages were imported. |
| 86 | func Register() { |
| 87 | syntax.Register(syntax.Definition{ |
| 88 | Language: Language, |
| 89 | Extensions: []string{".rs"}, |
| 90 | Highlight: Highlight, |
| 91 | }) |
| 92 | } |
| 93 | |
| 94 | // CargoBinDir returns where cargo puts the binaries it installs: CARGO_HOME/bin |
| 95 | // when CARGO_HOME is set, and ~/.cargo/bin otherwise. |
| 96 | // |
| 97 | // It is one of the two places rust-analyzer is looked for after PATH. |
| 98 | func CargoBinDir() string { |
| 99 | if home := os.Getenv("CARGO_HOME"); home != "" { |
| 100 | return filepath.Join(home, "bin") |
| 101 | } |
| 102 | home, err := os.UserHomeDir() |
| 103 | if err != nil { |
| 104 | return "" |
| 105 | } |
| 106 | return filepath.Join(home, ".cargo", "bin") |
| 107 | } |
| 108 | |
| 109 | // RustupToolchainBinDir returns rustup's own binary directory, |
| 110 | // RUSTUP_HOME/bin, defaulting to ~/.rustup/bin. |
| 111 | // |
| 112 | // `rustup component add rust-analyzer` — the command the install hint gives — |
| 113 | // puts a shim on PATH through ~/.cargo/bin, but a machine where the cargo |
| 114 | // directory was never added to PATH still has this one, and looking in both is |
| 115 | // cheaper than explaining the difference to somebody whose completion is |
| 116 | // silently missing. |
| 117 | func RustupToolchainBinDir() string { |
| 118 | if home := os.Getenv("RUSTUP_HOME"); home != "" { |
| 119 | return filepath.Join(home, "bin") |
| 120 | } |
| 121 | home, err := os.UserHomeDir() |
| 122 | if err != nil { |
| 123 | return "" |
| 124 | } |
| 125 | return filepath.Join(home, ".rustup", "bin") |
| 126 | } |