turbo-editors/turbo-moonbitpublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-moonbit.git
git clone ssh://git@rickub.com/turbo-editors/turbo-moonbit.git

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

summary.md · 111 lines · 16.9 KBmarkdown Blame HistoryRaw
📦 Turbo MoonBit cc1f595 k33g 11h ago1# turbo-moonbit — summary
2
3A snapshot of the present. Edited in place; the history is in `history.md`.
4
5## What this is
6
7A Turbo C-style terminal IDE for MoonBit, written in Go, built on **[turbo-core](https://rickub.com/turbo-editors/turbo-core)** — the library Turbo Go, Turbo Rust and Turbo Python already share. This repository holds the command, the profile that says the editor is for MoonBit, and the MoonBit scanner. Everything else — the event loop, the windows, the dialogs, the themes, the LSP client, the terminal emulator, the project tree, the snippets and tools machinery — is the library's, and none of it is copied here.
8
9Module `rickub.com/turbo-editors/turbo-moonbit`, `require`ing turbo-core **v0.4.2** from the module proxy with **no active `replace`**. The commented-out `replace` at the bottom of `go.mod` documents the escape hatch without being one; `01-release.tag.sh` refuses to tag a release whose `go.mod` carries a live one.
10
11## Layout
12
13| | |
14| --- | --- |
15| `main.go` | flags, the terminal, `moonbitlang.Register()`, the profile, the loop |
16| `internal/moonbitlang/moonbitlang.go` | `Name`, `Slug`, `Language`, `Profile()`, `Register()`, the server directories |
17| `internal/moonbitlang/scan.go` | the dispatcher, comments, attributes, multi-line string lines, package names, what follows a dot |
18| `internal/moonbitlang/literals.go` | the five quoted forms — `"…"`, `b"…"`, `re"…"`, `'c'`, `b'c'` |
19| `internal/moonbitlang/words.go` | numbers, keywords, labels, the identifier-case rule |
20| `internal/moonbitlang/*.toml.tmpl` | the three starter files, embedded by `templates.go` |
21| `diagram_test.go` | holds `docs/diagrams/packages.drawio` to `go list` |
22| `internal/moonbitlang/reference_test.go` | holds `docs/*/reference/languages.md` to the scanner, keyword row included |
23| `docs/{en,fr}/` | 34 pages each (README included), Diátaxis |
24| `demos/` | three MoonBit projects to open in the editor: `hello`, `shapes`, `syntax-tour` |
25
26## How to build, test and measure
27
28```bash
29make check # fmt, vet, then the whole suite — what a commit should pass
30make build # into bin/turbo-moonbit, then check the binary reports its version
31make install # build, install onto PATH, report what it found
32go test ./... # the moon-lsp tests skip themselves without the toolchain
33```
34
35Quality gate, separate from the tests:
36
37```bash
38python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .
39```
40
41To build against a turbo-core you have changed but not released:
42
43```bash
44go work init . ../turbo-core
45go list -f '{{.Dir}}' rickub.com/turbo-editors/turbo-core/app # must NOT be under pkg/mod
46```
47
48`go.work` and `go.work.sum` are gitignored. **Everything still builds and still passes** while testing the published library instead of your changes, so run that second line.
49
50## Decisions in force
51
52- **The scanner carries no state at all**, and it is the only one in this family that does not. MoonBit has no block comment, no literal that may reach the next line (a newline before a closing quote is an *unterminated literal* error), a multi-line string is a run of self-contained `#|` / `$|` lines, and an attribute is explicitly one line. The `carry` type is empty and named rather than `struct{}` inline, so the reasoning has somewhere to live and a future construct that crosses lines has somewhere to go.
53- **The scanner was written against the published lexical grammar**, not against examples. `docs.moonbitlang.com/en/latest/language/lexical-conventions.html` gives every literal production, the keyword list, and the sentence that decides `1..=2`. Where the scanner departs from it, the departure is named in `reference/languages.md`.
54- **There is no table of built-in types, and there does not need to be.** A `uident` "begins with an ASCII uppercase letter" is a *lexical* rule, so every capitalised name is a type by the same line of code. The cost is that an enum constructor of your own is coloured as a type; nothing in the syntax separates `Circle(1.0)` from a type applied to arguments.
55- **The prelude table was read out of `moonbitlang/core/prelude`'s generated `.mbti`**, not remembered. That is how `print` stayed out of it: MoonBit has `println` and has never had `print`, and the compiler confirmed it during the tutorial's first draft.
56- **A number's dot is only part of it when a second dot does not follow.** Without that, `1..=2` reads as the double `1.` and `.=2`, and every range in every file is miscoloured. Suffixes are upper case or they are not suffixes.
57- **A name after a dot is looked up without the keyword table**, because dot-identifiers "use the identifier case rules without consulting the keyword table, so `.if` is valid".
58- **A labelled argument's `name~` is `ClassAttribute`, tilde included.** The tilde appears in no MoonBit operator, so it can only be a label — except after a capital or a keyword, which the grammar excludes.
59- **An attribute takes the whole line**, because the grammar hands it everything through the next newline.
60- **`package` is coloured as a keyword in `.mbt` too**, although it is only reserved there. It is a real keyword in the `.mbti` files this editor also colours, and in a `.mbt` the colour says what the compiler is about to.
61- **The toolchain menu is `~M~oonBit`, not `moon`.** M is free — the fixed menus take F, E, S, R, C, O, W, N and H, which rules out both the O and the N of MoonBit. Named after the language because the menu holds whatever the project put in its tools file.
62- **The language server is `moon-lsp --stdio`**, and the install hint installs the whole toolchain: `curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash`. The `--stdio` is not optional — with no argument moon-lsp prints its usage and exits, which the editor would see as a server that died at once.
63- **moon-lsp answers seven of turbo-core's nine questions.** It advertises neither `typeDefinition` nor `implementation`, so those two items report nothing found. It *does* answer `workspace/symbol`, which Turbo Python's server does not. A test asserts both halves.
64- **Two directories are searched for the server besides `PATH`**: `$MOON_HOME/bin` and `~/.moon/bin`. The MoonBit installer's last act is to append its bin directory to one shell profile, which does nothing for an editor started from a shell that was already open.
65- **Root markers, in order: `moon.mod`, `moon.mod.json`.** `moon.work` is deliberately absent although `moon` itself looks for it: a workspace manifest only ever sits *above* a `moon.mod`, so naming it would make a rare case look like part of the rule.
66- **`Profile()` is a function, not a variable**, because `Server.Dirs` reads the environment and a variable would freeze whatever `MOON_HOME` said at link time.
67- **Snippet bodies are TOML *literal* multi-line strings** — three apostrophes, not three double quotes. MoonBit interpolates with `\{…}`, and a backslash before a brace is not one of TOML's escapes, so a body in basic strings would not parse and the file the editor had just offered to create would be refused when read back.
68- **Snippet bodies are indented two spaces**, which is what `moon fmt` writes. A snippet that disagrees with the formatter turns one insertion into a whole-file diff.
69- **Nine tools in the starter file**, `moon check` first because it answers "is this sound?" without producing anything. Three ask for a value and one names a menu of its own — both features are invisible otherwise.
70- **`autosave = true` in the starter settings file, `false` in `settings.Default()`.** Two statements in two places on purpose.
71- **The starter templates are embedded files, not Go constants**, with the `.tmpl` suffix because `settings.toml.tmpl` holds `theme = %q`, which is not valid TOML.
72- **The three demo projects must build, run and survive `moon fmt` unchanged.** They are the only MoonBit in this repository that a compiler ever sees, and they are what caught three things the scanner alone could not: `derive(Show)` is deprecated in favour of `derive(Debug)`, the functional `loop (a, b) { … }` form is deprecated in favour of `for i = a, j = b { … }`, and `typealias` is not accepted at top level by this compiler although it is in the grammar's keyword list. The first two were in the starter snippets file and are now fixed.
73- **Agent windows are turbo-core's, and what belongs here is the starter file.** `acp.toml.tmpl` is the fourth embedded template, and `profile.Templates.Agents` is the whole of Turbo MoonBit's contribution to the feature. The example agent is `docker agent serve acp .turbo-moonbit/agent.yaml`; the only other thing about this editor in it is the sentence saying a ```moonbit fence is coloured by the scanner this editor colours its own files with. Every other editor got agent windows the same way — one file, one line. The reasoning, and why it could not have been built here, is in `docs/*/explanation/agent-windows.md`.
74- **The starter agents file teaches the window's keyboard as well as the format.** `Enter`, `Alt-Enter`, `Tab`, `Esc`, `Ctrl-W` and the copying keys are all in its comments, because a file the editor hands you is the one document a user is guaranteed to see.
75
76## State as of 2026-09-09
77
78- **Complete and green.** `make check` passes; `gofmt -l` and `go vet` are clean. 374 tests and subtests.
79- **Quality gate: PASS**, twice. 0 errors, 0 warnings, 0 smells; total complexity 87, worst file 40 against a limit of 60.
80- **Documentation**: 34 files × EN + FR, a `README.md` at the root, and `docs/diagrams/packages.drawio` checked against `go list` by `diagram_test.go`.
81- **Every test was falsified.** 21 mutations of the scanner and profile, 16 of the templates, 5 of the language-server tests, 8 of the diagram/installer/Makefile checks — all caught. Two genuinely weak tests were found this way and strengthened: one that would have passed with the language list missing from the snippets comment, and one that missed a literal running past its closing quote.
82- **Verified in a real pty**, not only by tests: the bar reading ` File Edit Search Run Code Options Window Snippets MoonBit Help`; `Alt-M` opening the MoonBit menu with `Create tools file` available (`30;42`) and `Open tools file` greyed (`90;47`), then the two swapping once the file exists; keywords `97;44;1`, types `96;44`, functions `93;44;1`, builtins `96;44;1`, strings `92;44`, numbers `95;44`, comments `38;2;143;143;143`; a string span covering its `\{…}` interpolations whole; the About box reading **"A Turbo C-style editor for MoonBit, / written in Go."**; `moon fmt` run from the menu adding a trailing comma and the editor reloading the file; and — the one that matters — **a file that does not compile, opened by a relative path, showing `×` in the gutter (`91;44;1`) and `⚠ The value identifier undefined_name is unbound.` on the status bar**.
83- **The arrow counts in the tutorial were read off a pty**, not counted by hand: five `→` from File reaches Options, eight reaches MoonBit.
84- **The server is found outside `PATH`** — `scripts/install.sh` located it at `~/.moon/bin/moon-lsp` and the profile searches the same places.
85- **The editor is registered in the family.** turbo-core's `README.md`, both doc `README`s, `profile/profile.go`'s package comment, both workspace how-tos and `.memory/summary.md` now count four editors. turbo-core's `.go` strings were swept for hardcoded language names; **every hit was a false positive, a doc-comment example of the seam, or the true statement that these editors are written in Go.** No library change was needed.
86
87## Known boundaries
88
89- **A `.mbt` created in the editor is diagnosed from its first save — with turbo-core ≥ v1.0.2.** moon-lsp lists a package's files from the directory, so a document it was told is open but never told *exists* was never diagnosed; turbo-core now sends `workspace/didChangeWatchedFiles` when a save creates the file. Until 2026-09-19 this was pinned as a limit (`TestAFileCreatedAfterTheServerStartedIsNotDiagnosed`); that test went red on macOS, where moon-lsp notices new files by itself, and is now `TestAFileCreatedInTheEditorIsDiagnosedFromItsFirstSaveWithRealMoonLSP` — which fails against turbo-core v1.0.1 and passes from v1.0.2. `enable-completion.md` had promised this since 2026-09-18. The test helper `typeText` now sends Enter for a newline; typed as a rune, `\n` was dropped and a four-line fixture became one `///|` comment line.
90- **In `turbo-classic` alone, `syntax.attribute` and `syntax.identifier` are both plain yellow**, so a MoonBit attribute or label is not told apart from an ordinary name in that one theme. The other seven distinguish them. It is a turbo-core theme matter that hits Turbo Rust's `#[derive]` equally, so it was not fixed from here.
91- **A non-ASCII identifier is left uncoloured.** MoonBit allows CJK and other Unicode ranges in a name; turbo-core's rune predicates are ASCII.
92- **A string nested inside an interpolation ends the outer literal.** `"a \{b} c"` is one span, but `"a \{f("x")} c"` scans as string, then `x` as an identifier, then string, because the first unescaped quote is taken as the closer. The grammar says nested literals do not count, so a correct implementation needs the parser. The spans stay ordered and non-overlapping, so nothing downstream misbehaves. Pinned by `TestAStringInsideAnInterpolationEndsTheOuterLiteral`, and `demos/syntax-tour/tour.mbt` carries a labelled line that shows it.
93
94## Not yet established
95
96- **Agent windows have never been opened in this editor.** The feature is turbo-core's and was driven end to end from turbo-go against a real `docker agent` and a real llama.cpp; what is here is the starter file, covered by tests that create it, load it back and check it names this editor's own language. Nobody has run `turbo-moonbit`, pressed `Alt-A` and talked to an agent from it.
97
98
99- **Run on macOS once (2026-09-19), by the user's `make check`, and it found a turbo-core defect**: symlinked temp dirs (`/var``/private/var`) made moon-lsp treat the fixture as a file of no package. Fixed in turbo-core v1.0.1; this editor must re-pin before releasing. Never run on Windows.
100- **No CI.** There is no pipeline configuration in the repository.
101- **Never released.** No tag exists; `01``04` have only ever been read here, never run.
102- **Performance on a large file is unmeasured.** The scanner is a line at a time and carries nothing, but nothing has been timed.
103- **`.mbtx` scripts are claimed but untested against a real one.** The extension is registered and coloured; no `.mbtx` file has been opened in the editor.
104- **Nothing checks the demos automatically.** They were built, run and formatted by hand; no test or CI step re-runs `moon check` over them, so a future MoonBit release could deprecate something in them without anything noticing.
105- **`moon.work` workspaces are untested.** The root markers deliberately stop at the module, and no multi-module project has been opened.
106
107## State as of 2026-09-19 — moved to Rickub, released by a workflow
108
109- **Module path `rickub.com/turbo-editors/turbo-moonbit`**, depending on `rickub.com/turbo-editors/turbo-core v1.0.0` — the first turbo-core version published under that path (`v0.9.0` on the proxy still declares the Codeberg path and cannot be required as `rickub.com/…`). Every import, the Makefile's `VERSION_PKG`, `scripts/install.sh`, the README and the docs say `rickub.com`. `GOWORK=off make check` green. The repository on this side is a fresh `git init` with `origin` at `ssh://git@rickub.com/turbo-editors/turbo-moonbit.git` and **no commit yet**; `01-release.tag.sh` makes the first one.
110- **Releases are one script and one workflow**, modelled on turbo-core's and identical to turbo-go's. `01-release.tag.sh` runs `make check` under `TURBO_MOONBIT_RELEASING=1`, refuses a tag taken locally or on origin (bump, never move), refuses a `replace` in `go.mod`, commits, pushes the current branch, then tags and pushes the tag. That push starts `.github/workflows/release.yml`: `go test` with `TURBO_MOONBIT_RELEASING=1`, `./02-build-releases.sh "${GITHUB_REF_NAME}"`, release notes from the tag message, a run artifact, then `softprops/action-gh-release@v2` attaching `turbo-moonbit-*`, `SHA256SUMS` and `README.md` with the job's own `GITHUB_TOKEN` — the only credential Rickub's release API accepts. **`02-release.publish.sh` and `04-release.upload-binaries.sh` are gone**; the build script is now `02-build-releases.sh`, takes the tag as `$1` (CI has no `release.env`), validates it, refuses a `replace`, and starts from an empty `release/${TAG}/`. `release.env` holds only `TAG` and `ABOUT`; `turbo-moonbit.token.env` is read by nothing.
111- **`release_test.go`** runs `01` for real against a throwaway bare remote (publishes, then refuses the same tag), runs `02` alone to see it refuse `v0.o.0`, and asserts the workflow's trigger, `contents: write`, `./02-build-releases.sh`, `fail_on_unmatched_files`, docs linked at the tag, no `secrets.`, and `TURBO_MOONBIT_RELEASING`. The copy of the module for the clone leaves out `.git`, `bin`, `release`, `kits`, `demo`, `demos`, `*.env` and `go.work*`; children run with `GOWORK=off`.