turbo-editors/turbo-gopublic Fork 0
v1.0.3
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-go.git
git clone ssh://git@rickub.com/turbo-editors/turbo-go.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

main.go · 241 lines · 7.2 KBGo Blame HistoryRaw
📦 Turbo Go 3d7798b k33g yesterday1// Command turbo-go is a Turbo C-style editor for Go: a full-screen terminal
2// IDE with menus, movable windows, syntax colouring and gopls completion.
3//
4// Almost all of it is turbo-core, the library every Turbo editor is built on.
5// What is here is the command line, the terminal, and internal/golang — the
6// profile that says this one is for Go.
7//
8// Usage:
9//
10// turbo-go [flags] [file...]
11//
12// Flags:
13//
14// -theme name the colour theme to start with, overriding the project's
15// -list-themes print the available themes and exit
16// -no-lsp do not start a language server
17// -version print the version and exit
📦 Turbo Go 85cf23f k33g 7h ago18// -load-config url copy the .turbo-go directory found at a repository URL into the working directory and exit
📦 Turbo Go 3d7798b k33g yesterday19package main
20
21import (
22 "context"
23 "errors"
24 "flag"
25 "fmt"
26 "os"
📦 Turbo Go 85cf23f k33g 7h ago27 "strings"
📦 Turbo Go 3d7798b k33g yesterday28
29 "github.com/gdamore/tcell/v2"
30
31 "rickub.com/turbo-editors/turbo-core/app"
📦 Turbo Go 85cf23f k33g 7h ago32 "rickub.com/turbo-editors/turbo-core/configrepo"
📦 Turbo Go 3d7798b k33g yesterday33 "rickub.com/turbo-editors/turbo-core/profile"
34 "rickub.com/turbo-editors/turbo-core/settings"
35 "rickub.com/turbo-editors/turbo-core/theme"
36 "rickub.com/turbo-editors/turbo-core/version"
37
38 "rickub.com/turbo-editors/turbo-go/internal/golang"
39)
40
41func main() {
42 if err := run(); err != nil {
43 fmt.Fprintf(os.Stderr, "%s: %v\n", golang.Slug, err)
44 os.Exit(1)
45 }
46}
47
48// options are what the command line asked for.
49type options struct {
50 theme string
51 listThemes bool
📦 Turbo Go 85cf23f k33g 7h ago52 loadConfig string
📦 Turbo Go 3d7798b k33g yesterday53 noLSP bool
54 version bool
55 files []string
56}
57
58// parseFlags reads the command line.
59func parseFlags() options {
60 var opts options
61
62 // The default is empty rather than the theme's name so that "was -theme
63 // given?" can still be answered afterwards, which is what lets the project
64 // settings fill it in without overriding an explicit choice.
65 flag.StringVar(&opts.theme, "theme", "", "colour theme to start with (default from the project, else "+theme.DefaultName+")")
66 flag.BoolVar(&opts.listThemes, "list-themes", false, "print the available themes and exit")
67 flag.BoolVar(&opts.noLSP, "no-lsp", false, "do not start a language server")
68 flag.BoolVar(&opts.version, "version", false, "print the version and exit")
📦 Turbo Go 85cf23f k33g 7h ago69 flag.StringVar(&opts.loadConfig, "load-config", "", "copy the .turbo-go directory found at this repository URL into the working directory, then exit")
📦 Turbo Go 3d7798b k33g yesterday70 flag.Parse()
71
72 opts.files = flag.Args()
73 return opts
74}
75
76// run does the work, so that main is nothing but error reporting.
77func run() error {
78 opts := parseFlags()
79 // Registering here rather than from an init function is what makes "this
80 // editor knows Go" a line somebody can read.
81 golang.Register()
82 p := golang.Profile()
83
84 switch {
85 case opts.version:
86 fmt.Printf("%s %s\n", p.Name, version.Current())
87 return nil
88 case opts.listThemes:
89 return listThemes(p)
📦 Turbo Go 85cf23f k33g 7h ago90 case opts.loadConfig != "":
91 return loadConfig(p, opts.loadConfig)
📦 Turbo Go 3d7798b k33g yesterday92 }
93
94 return edit(opts, p)
95}
96
97// listThemes prints every theme that can be loaded, with its description.
98func listThemes(p profile.Profile) error {
99 userDir := p.ThemeDir()
100
101 for _, name := range theme.Available(userDir) {
102 loaded, err := theme.Load(name, userDir)
103 if err != nil {
104 fmt.Printf("%-16s (cannot be loaded: %v)\n", name, err)
105 continue
106 }
107 fmt.Printf("%-16s %s\n", name, loaded.Description())
108 }
109
110 if userDir != "" {
111 fmt.Printf("\nYour own themes go in %s\n", userDir)
112 }
113 return nil
114}
115
📦 Turbo Go 85cf23f k33g 7h ago116// loadConfig copies a shared configuration — the .turbo-go directory at a
117// repository URL, on rickub, GitHub, GitLab or Codeberg — into the working
118// directory, and says what arrived. The editor is not started: this is a
119// set-up step, and the next command is the one that opens the project.
120//
121// A project that already has a .turbo-go is left alone; turbo-core refuses
122// rather than overwrite what the project decided.
123func loadConfig(p profile.Profile, url string) error {
124 result, err := configrepo.Load(context.Background(), p, url, ".")
125 if err != nil {
126 return err
127 }
128 fmt.Print(describeLoad(result))
129 return nil
130}
131
132// describeLoad is what a successful -load-config prints: where the files
133// came from, and each of them, so the reader knows what is now in force.
134func describeLoad(result configrepo.Result) string {
135 var out strings.Builder
136 fmt.Fprintf(&out, "Copied %s from %s", result.Dir, result.Remote)
137 if result.Ref != "" {
138 fmt.Fprintf(&out, " (%s)", result.Ref)
139 }
140 out.WriteString(":\n")
141 for _, file := range result.Files {
142 fmt.Fprintf(&out, " %s\n", file)
143 }
144 return out.String()
145}
146
📦 Turbo Go 3d7798b k33g yesterday147// edit opens the terminal and runs the editor until the user leaves.
148func edit(opts options, p profile.Profile) error {
149 project, projectSettings := loadProjectSettings(p)
150
151 screen, err := newScreen()
152 if err != nil {
153 return err
154 }
155 // The screen must be given back whatever happens, or a crash leaves the
156 // terminal in raw mode with no cursor.
157 defer screen.Fini()
158
159 editor := app.New(screen, themeName(opts, projectSettings), p)
160 if settings.Exists(p, project) {
161 editor.UseSettings(projectSettings, settings.Path(p, project))
162 }
163 openFiles(editor, opts.files)
164
165 ctx, cancel := context.WithCancel(context.Background())
166 defer cancel()
167 if !opts.noLSP {
168 editor.StartLanguageServer(ctx, app.ProjectRoot(p, opts.files))
169 }
170 defer editor.Language().Stop(context.Background())
171
172 return editor.Run()
173}
174
175// loadProjectSettings reads .turbo-go/settings.toml from the working
176// directory, and returns that directory along with what it found.
177//
178// The working directory alone is looked in, with no walk up towards the root:
179// "the project" is where you started the editor, which is a rule you can hold
180// in your head. A file that is there but unreadable is reported on standard
181// error and then ignored — a broken settings file must not stop the editor
182// opening, because the editor is how you would fix it.
183func loadProjectSettings(p profile.Profile) (string, settings.Settings) {
184 project, err := os.Getwd()
185 if err != nil {
186 project = "."
187 }
188
189 loaded, err := settings.Load(p, project)
190 switch {
191 case errors.Is(err, settings.ErrNotFound):
192 return project, settings.Default()
193 case err != nil:
194 fmt.Fprintf(os.Stderr, "%s: %v\n", golang.Slug, err)
195 return project, settings.Default()
196 }
197 return project, loaded
198}
199
200// themeName decides which theme to start in.
201//
202// A -theme flag wins, because it is the more explicit statement of the two and
203// is how you try a theme without editing a file everyone shares. The project's
204// settings come next, and the built-in default last.
205func themeName(opts options, projectSettings settings.Settings) string {
206 switch {
207 case opts.theme != "":
208 return opts.theme
209 case projectSettings.Theme != "":
210 return projectSettings.Theme
211 default:
212 return theme.DefaultName
213 }
214}
215
216// newScreen opens the terminal and turns on what the editor needs from it.
217func newScreen() (tcell.Screen, error) {
218 screen, err := tcell.NewScreen()
219 if err != nil {
220 return nil, fmt.Errorf("opening the terminal: %w", err)
221 }
222 if err := screen.Init(); err != nil {
223 return nil, fmt.Errorf("initialising the terminal: %w", err)
224 }
225
226 screen.EnableMouse()
227 screen.EnablePaste()
228 return screen, nil
229}
230
231// openFiles opens the files named on the command line, or an empty window when
232// none were.
233func openFiles(editor *app.App, files []string) {
234 if len(files) == 0 {
235 editor.NewFile()
236 return
237 }
238 for _, file := range files {
239 editor.Open(file)
240 }
241}