1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
// 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")
}
|