// Package golang is everything about Turbo Go that is about *Go*: how the // editor names itself, which language server it talks to, what a Go project's // starter files say, and how Go source is coloured. // // Everything else the editor does lives in turbo-core, which knows nothing // about Go. This package is the whole of the difference between Turbo Go and // Turbo Rust, which is what makes a third editor a matter of writing one of // these rather than forking anything. // // golang.Register() // teach the library to colour Go // editor := app.New(screen, name, golang.Profile()) package golang 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-go) and the stem of its environment // variables (as TURBO_GO_…), so it is not free to change. const ( Name = "Turbo Go" Slug = "turbo-go" ) // Language is the name Go is known by: the value LanguageOf returns for a .go // file, and what a snippets file writes in its languages key. const Language syntax.Language = "go" // ServerCommand is the language server Turbo Go talks to, and InstallHint the // single command that installs it. const ( ServerCommand = "gopls" InstallHint = "go install golang.org/x/tools/gopls@latest" ) // Profile returns the editor Turbo Go is. // // It is a function rather than a variable because Server.Dirs is worked out // from the environment, and a variable would freeze whatever GOPATH said when // the package was linked. func Profile() profile.Profile { return profile.Profile{ Name: Name, Slug: Slug, Language: "Go", // Go is fixed on the bar and takes G, which no other menu claims. ToolsMenu: "~G~o", // go.mod is the module's boundary, and the module is what gopls loads. RootMarkers: []string{"go.mod"}, Server: profile.Server{ Command: ServerCommand, // gopls needs a subcommand; most language servers do not. Args: []string{"serve"}, InstallHint: InstallHint, Dirs: []string{BinDir()}, }, Templates: profile.Templates{ Settings: settingsTemplate, Snippets: snippetsTemplate, Tools: toolsTemplate, Agents: agentsTemplate, }, } } // Register teaches turbo-core to colour Go. // // 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{".go"}, Highlight: highlight, }) } // highlight colours Go source with go/scanner — the same lexer the Go toolchain // uses, so the editor and the compiler agree about what a token is. // // It works in byte offsets rather than a line at a time, which is why it goes // through syntax.LineIndex instead of syntax.LineScanner. func highlight(src string) [][]syntax.Span { lines := syntax.NewLineIndex(src) out := make([][]syntax.Span, lines.Count()) tokens := scanTokens(src) for i, class := range classify(tokens) { lines.AppendSpans(out, tokens[i].start, tokens[i].end, class) } return out } // BinDir returns where "go install" puts binaries: GOBIN when it is set, // GOPATH/bin otherwise, and the conventional ~/go/bin when neither is. // // It is where gopls is looked for after PATH, because `go install` puts it // somewhere that is very often not on PATH — which is the single most common // reason completion is missing on a machine that has gopls. func BinDir() string { if gobin := os.Getenv("GOBIN"); gobin != "" { return gobin } if gopath := os.Getenv("GOPATH"); gopath != "" { return filepath.Join(gopath, "bin") } home, err := os.UserHomeDir() if err != nil { return "" } return filepath.Join(home, "go", "bin") }