// Package rustlang is everything about Turbo Rust that is about *Rust*: how the // editor names itself, which language server it talks to, what a Cargo // project's starter files say, and how Rust source is coloured. // // Everything else the editor does lives in turbo-core, which knows nothing // about Rust. This package is the whole of the difference between Turbo Rust // and Turbo Go, which is what makes a third editor a matter of writing one of // these rather than forking anything. // // rustlang.Register() // teach the library to colour Rust // editor := app.New(screen, name, rustlang.Profile()) package rustlang import ( "os" "path/filepath" "rickub.com/turbo-editors/turbo-core/profile" "rickub.com/turbo-editors/turbo-core/syntax" ) // Name and Slug are what the editor calls itself. The slug is also its binary, // its project directory (as .turbo-rust) and the stem of its environment // variables (as TURBO_RUST_…), so it is not free to change. const ( Name = "Turbo Rust" Slug = "turbo-rust" ) // Language is the name Rust is known by: the value LanguageOf returns for a .rs // file, and what a snippets file writes in its languages key. const Language syntax.Language = "rust" // ServerCommand is the language server Turbo Rust talks to, and InstallHint the // single command that installs it. // // rustup is the way nearly everybody has Rust, and `rustup component add` is // the shortest true answer for them. Somebody who installed Rust from their // distribution will have their own package for it; the hint has to fit on a // status bar, so it names the common case. const ( ServerCommand = "rust-analyzer" InstallHint = "rustup component add rust-analyzer" ) // Profile returns the editor Turbo Rust is. // // It is a function rather than a variable because Server.Dirs is worked out // from the environment, and a variable would freeze whatever CARGO_HOME said // when the package was linked. func Profile() profile.Profile { return profile.Profile{ Name: Name, Slug: Slug, Language: "Rust", // R is Run's and S is Search's, so the hot key lands on the T. The // alternative was naming the menu Cargo, which would have taken C — but // the menu holds whatever the project put in its tools file, and that is // not always cargo. ToolsMenu: "Rus~t~", // Cargo.toml is the crate's boundary, and the crate is what // rust-analyzer loads. A workspace root has one too, and the nearest one // going up is the right answer for both. RootMarkers: []string{"Cargo.toml"}, Server: profile.Server{ Command: ServerCommand, // rust-analyzer takes no subcommand, unlike gopls. Args: nil, InstallHint: InstallHint, Dirs: []string{CargoBinDir(), RustupToolchainBinDir()}, }, Templates: profile.Templates{ Settings: settingsTemplate, Snippets: snippetsTemplate, Tools: toolsTemplate, Agents: agentsTemplate, }, } } // Register teaches turbo-core to colour Rust. // // It is called explicitly at start-up rather than from an init function so that // "which languages does this editor know?" is answered by reading main, not by // working out which packages were imported. func Register() { syntax.Register(syntax.Definition{ Language: Language, Extensions: []string{".rs"}, Highlight: Highlight, }) } // CargoBinDir returns where cargo puts the binaries it installs: CARGO_HOME/bin // when CARGO_HOME is set, and ~/.cargo/bin otherwise. // // It is one of the two places rust-analyzer is looked for after PATH. func CargoBinDir() string { if home := os.Getenv("CARGO_HOME"); home != "" { return filepath.Join(home, "bin") } home, err := os.UserHomeDir() if err != nil { return "" } return filepath.Join(home, ".cargo", "bin") } // RustupToolchainBinDir returns rustup's own binary directory, // RUSTUP_HOME/bin, defaulting to ~/.rustup/bin. // // `rustup component add rust-analyzer` — the command the install hint gives — // puts a shim on PATH through ~/.cargo/bin, but a machine where the cargo // directory was never added to PATH still has this one, and looking in both is // cheaper than explaining the difference to somebody whose completion is // silently missing. func RustupToolchainBinDir() string { if home := os.Getenv("RUSTUP_HOME"); home != "" { return filepath.Join(home, "bin") } home, err := os.UserHomeDir() if err != nil { return "" } return filepath.Join(home, ".rustup", "bin") }