turbo-editors/turbo-gopublic Fork 0
main
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.

📦 Turbo Go 3d7798b · on main · k33g · 8h ago
architecture.md · 85 lines · 5.4 KBmarkdown
Blame HistoryOpen raw

Architecture — explanation

What is this about?

Turbo Go is a command, a profile and a scanner. Everything else — the editing widget, the windows, the menus, the dialogs, the themes, the terminal emulator, the file tree, the LSP client — is turbo-core, the library every Turbo editor is built on.

That was not always true. Turbo Go was a single program of about eleven and a half thousand lines in fourteen packages until a second editor was wanted, at which point the fourteen moved into a library and one stayed here. This page is about the split that resulted.

What is in this repository

main.go              flags, the terminal, and the wiring
internal/golang      the whole of what makes this Turbo Go
  golang.go          the profile: name, menu, server, root marker
  scan.go            the Go scanner, on top of go/scanner
  templates.go       three //go:embed declarations
  *.toml.tmpl        the three starter files a project gets, embedded

About four hundred lines. There is no internal/app, no internal/ui, no internal/buffer — those exist once, in the library, and every editor built on it uses them unchanged.

What main does

Six things, in this order:

  1. Parses the flags.
  2. Calls golang.Register(), which teaches the library to colour .go files.
  3. Builds golang.Profile() — the value that says this editor is Turbo Go.
  4. Reads .turbo-go/settings.toml from the working directory, if there is one.
  5. Opens the terminal and hands the screen, the theme name and the profile to app.New.
  6. Starts gopls in the module root, and runs the event loop.

That is the whole command. Every decision it makes — which theme wins, which files to open, whether to start a language server — is about this run, not about Go.

The profile is the seam

profile.Profile{
	Name:        "Turbo Go",
	Slug:        "turbo-go",
	Language:    "Go",
	ToolsMenu:   "~G~o",
	RootMarkers: []string{"go.mod"},
	Server:      profile.Server{Command: "gopls", Args: []string{"serve"}, …},
	Templates:   profile.Templates{Settings: …, Snippets: …, Tools: …},
}

Everything that used to be a hardcoded "turbo-go", "gopls" or "go.mod" somewhere in eleven thousand lines is one field here. The library reads them; nothing in the library knows what any of them mean.

Slug carries more than it looks. The binary is turbo-go, the project directory is .turbo-go, the user's own configuration lives in ~/.config/turbo-go, and the environment variables that override it are TURBO_GO_THEME_DIR and TURBO_GO_SNIPPET_DIR — all derived from that one word. Those names are unchanged by the refactoring, deliberately: somebody who set TURBO_GO_THEME_DIR did so against a released binary.

Why the Go scanner is here and not in the library

turbo-core colours eight languages itself: TOML, YAML, Markdown, JavaScript, HTML, XML, Dockerfiles and shell. Those are the ones every editor meets whatever it is for — a project's configuration is TOML or YAML, its documentation is Markdown, its scripts are shell, its image build a Dockerfile.

Go is not one of them. The language that defines an editor is registered by that editor, which is why a .rs file opens as plain text here and a .go file opens as plain text in Turbo Rust.

The Go scanner is also the one that is least like the others. Every language in the library is scanned a line at a time with syntax.LineScanner; Go goes through go/scanner, the lexer the Go toolchain itself uses, and converts its byte offsets with syntax.LineIndex. That the library supports both shapes is because of this scanner.

What moved, and what did not

Was Is
internal/buffer, internal/ui, internal/editor, … turbo-core/buffer, turbo-core/ui, turbo-core/editor, …
internal/syntax — six languages turbo-core/syntax — eight, plus a registry; Go lives here
internal/app with a Name constant turbo-core/app taking a profile.Profile
internal/lsp hardcoding gopls turbo-core/lsp taking a profile.Server
moduleRoot in main.go app.ProjectRoot(p, files), with go.mod in the profile
settings.DirName = ".turbo-go" p.ProjectDir()

Nothing about the editor's behaviour changed. The menus, the keys, the themes, the file formats and the environment variables are what they were. What changed is where the code lives.

Why a library rather than a fork

The alternative to extracting turbo-core was copying Turbo Go and changing the Go bits. It was rejected before it was started: two copies of eleven thousand lines drift within a month, and every fix has to be made twice by somebody who remembers there are two.

The cost, accepted: a change to a menu now affects every editor at once, and Turbo Go can no longer make a decision that suits only Go without either putting it in the profile or arguing for it in the library. That is a real constraint, and it is the one that keeps the editors the same editor.

How it relates to the rest

 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
# Architecture — explanation

## What is this about?

Turbo Go is a command, a profile and a scanner. Everything else — the editing widget, the windows, the menus, the dialogs, the themes, the terminal emulator, the file tree, the LSP client — is [turbo-core](https://rickub.com/turbo-editors/turbo-core), the library every Turbo editor is built on.

That was not always true. Turbo Go was a single program of about eleven and a half thousand lines in fourteen packages until a second editor was wanted, at which point the fourteen moved into a library and one stayed here. This page is about the split that resulted.

## What is in this repository

```
main.go              flags, the terminal, and the wiring
internal/golang      the whole of what makes this Turbo Go
  golang.go          the profile: name, menu, server, root marker
  scan.go            the Go scanner, on top of go/scanner
  templates.go       three //go:embed declarations
  *.toml.tmpl        the three starter files a project gets, embedded
```

About four hundred lines. There is no `internal/app`, no `internal/ui`, no `internal/buffer` — those exist once, in the library, and every editor built on it uses them unchanged.

## What `main` does

Six things, in this order:

1. Parses the flags.
2. Calls `golang.Register()`, which teaches the library to colour `.go` files.
3. Builds `golang.Profile()` — the value that says this editor is Turbo Go.
4. Reads `.turbo-go/settings.toml` from the working directory, if there is one.
5. Opens the terminal and hands the screen, the theme name and the profile to `app.New`.
6. Starts gopls in the module root, and runs the event loop.

That is the whole command. Every decision it makes — which theme wins, which files to open, whether to start a language server — is about *this run*, not about Go.

## The profile is the seam

```go
profile.Profile{
	Name:        "Turbo Go",
	Slug:        "turbo-go",
	Language:    "Go",
	ToolsMenu:   "~G~o",
	RootMarkers: []string{"go.mod"},
	Server:      profile.Server{Command: "gopls", Args: []string{"serve"}, },
	Templates:   profile.Templates{Settings: , Snippets: , Tools: },
}
```

Everything that used to be a hardcoded `"turbo-go"`, `"gopls"` or `"go.mod"` somewhere in eleven thousand lines is one field here. The library reads them; nothing in the library knows what any of them mean.

`Slug` carries more than it looks. The binary is `turbo-go`, the project directory is `.turbo-go`, the user's own configuration lives in `~/.config/turbo-go`, and the environment variables that override it are `TURBO_GO_THEME_DIR` and `TURBO_GO_SNIPPET_DIR` — all derived from that one word. Those names are unchanged by the refactoring, deliberately: somebody who set `TURBO_GO_THEME_DIR` did so against a released binary.

## Why the Go scanner is here and not in the library

turbo-core colours eight languages itself: TOML, YAML, Markdown, JavaScript, HTML, XML, Dockerfiles and shell. Those are the ones every editor meets whatever it is for — a project's configuration is TOML or YAML, its documentation is Markdown, its scripts are shell, its image build a Dockerfile.

Go is not one of them. The language that *defines* an editor is registered by that editor, which is why a `.rs` file opens as plain text here and a `.go` file opens as plain text in Turbo Rust.

The Go scanner is also the one that is *least* like the others. Every language in the library is scanned a line at a time with `syntax.LineScanner`; Go goes through `go/scanner`, the lexer the Go toolchain itself uses, and converts its byte offsets with `syntax.LineIndex`. That the library supports both shapes is because of this scanner.

## What moved, and what did not

| Was | Is |
| --- | --- |
| `internal/buffer`, `internal/ui`, `internal/editor`, … | `turbo-core/buffer`, `turbo-core/ui`, `turbo-core/editor`, … |
| `internal/syntax` — six languages | `turbo-core/syntax` — eight, plus a registry; Go lives here |
| `internal/app` with a `Name` constant | `turbo-core/app` taking a `profile.Profile` |
| `internal/lsp` hardcoding gopls | `turbo-core/lsp` taking a `profile.Server` |
| `moduleRoot` in `main.go` | `app.ProjectRoot(p, files)`, with `go.mod` in the profile |
| `settings.DirName = ".turbo-go"` | `p.ProjectDir()` |

**Nothing about the editor's behaviour changed.** The menus, the keys, the themes, the file formats and the environment variables are what they were. What changed is where the code lives.

## Why a library rather than a fork

The alternative to extracting turbo-core was copying Turbo Go and changing the Go bits. It was rejected before it was started: two copies of eleven thousand lines drift within a month, and every fix has to be made twice by somebody who remembers there are two.

The cost, accepted: a change to a menu now affects every editor at once, and Turbo Go can no longer make a decision that suits only Go without either putting it in the profile or arguing for it in the library. That is a real constraint, and it is the one that keeps the editors the same editor.

## How it relates to the rest

- What each of the library's packages does: [turbo-core's package reference](https://rickub.com/turbo-editors/turbo-core/blob/main/docs/en/reference/packages.md)
- How the colouring works here: [Colouring and completion](colouring-and-completion.md)
- Why the tools menu is data: [Go tools](go-tools.md)
- The decisions that outlived the refactoring: [Design decisions](design-decisions.md)