turbo-editors/turbo-moonbitpublic Fork 0
ded61deede2aab942065647372984c8d3179037b
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.

moonbitlang.go · 179 lines · 7.2 KBGo Blame HistoryRaw
📦 Turbo MoonBit cc1f595 k33g yesterday1// Package moonbitlang is everything about Turbo MoonBit that is about
2// *MoonBit*: how the editor names itself, which language server it talks to,
3// what a project's starter files say, and how MoonBit source is coloured.
4//
5// Everything else the editor does lives in turbo-core, which knows nothing
6// about MoonBit. This package is the whole of the difference between Turbo
7// MoonBit and Turbo Python, which is what makes a fifth editor a matter of
8// writing one of these rather than forking anything.
9//
10// moonbitlang.Register() // teach the library to colour MoonBit
11// editor := app.New(screen, name, moonbitlang.Profile())
12package moonbitlang
13
14import (
15 "os"
16 "path/filepath"
17
18 "rickub.com/turbo-editors/turbo-core/profile"
19 "rickub.com/turbo-editors/turbo-core/syntax"
20)
21
22// Name and Slug are what the editor calls itself. The slug is also its binary,
23// its project directory (as .turbo-moonbit) and the stem of its environment
24// variables (as TURBO_MOONBIT_…), so it is not free to change.
25const (
26 Name = "Turbo MoonBit"
27 Slug = "turbo-moonbit"
28)
29
30// Language is the name MoonBit is known by: the value LanguageOf returns for a
31// .mbt file, and what a snippets file writes in its languages key.
32//
33// It is lower case because every other name in the registry is — "moonbit"
34// beside "toml" and "dockerfile" — while the language's own spelling, the one
35// a person reads, is Profile().Language.
36const Language syntax.Language = "moonbit"
37
38// ServerCommand is the language server Turbo MoonBit talks to, and InstallHint
39// the single command that installs it.
40//
41// moon-lsp ships inside the MoonBit toolchain rather than being installed
42// separately, so the hint installs the whole toolchain: the same one command
43// puts moon, moonc and moon-lsp in place, and a machine that has moon but not
44// moon-lsp is not a machine anybody has.
45//
46// It answers seven of the nine questions turbo-core asks — completion, hover,
47// definition, references, the file's symbols and the project's symbols — and
48// publishes diagnostics unasked. It advertises neither typeDefinition nor
49// implementation, so those two items report nothing found; that is documented
50// rather than worked around, and a test asserts it so a future moon-lsp gaining
51// them is noticed.
52const (
53 ServerCommand = "moon-lsp"
54 InstallHint = "curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash"
55)
56
57// ServerArgs is what moon-lsp is started with.
58//
59// The --stdio is not optional: moon-lsp with no argument prints its usage and
60// exits, which the editor would see as a server that died at once. It is a
61// function rather than a variable so that no caller can append to the package's
62// own slice.
63func ServerArgs() []string { return []string{"--stdio"} }
64
65// Profile returns the editor Turbo MoonBit is.
66//
67// It is a function rather than a variable because Server.Dirs is worked out
68// from the environment, and a variable would freeze whatever MOON_HOME said
69// when the package was linked.
70func Profile() profile.Profile {
71 return profile.Profile{
72 Name: Name,
73 Slug: Slug,
74 // The language's own spelling, camel case and all. This is what the
75 // About box and the status bar read out, so it is written the way the
76 // people who made it write it.
77 Language: "MoonBit",
78 // M is free: the fixed menus take F, E, S, R, C, O, W, N and H — which
79 // rules out both the O and the N of MoonBit — so the hot key lands on
80 // the first letter of the word, which is the reading that costs nobody
81 // a second glance. The menu is named after the language and not after
82 // moon, because it holds whatever the project put in its tools file,
83 // and the first tools file anybody writes outgrows the language's own
84 // toolchain.
85 ToolsMenu: "~M~oonBit",
86 // moon.mod is what `moon new` writes today; moon.mod.json is the
87 // deprecated JSON form, still found in every project written before
88 // the change and still understood by the toolchain. The nearest one
89 // going up is the directory the server is started in.
90 //
91 // moon.work — the multi-module workspace manifest — is deliberately
92 // absent, although moon itself looks for it. It only ever sits *above*
93 // a moon.mod, so it could only be reached for a file lying loose in a
94 // workspace root, and naming it would make that rare case look like
95 // part of the rule.
96 RootMarkers: []string{"moon.mod", "moon.mod.json"},
97 Server: profile.Server{
98 Command: ServerCommand,
99 Args: ServerArgs(),
100 InstallHint: InstallHint,
101 Dirs: ServerDirs(),
102 },
103 Templates: profile.Templates{
104 Settings: settingsTemplate,
105 Snippets: snippetsTemplate,
106 Tools: toolsTemplate,
107 Agents: agentsTemplate,
108 },
109 }
110}
111
112// Register teaches turbo-core to colour MoonBit.
113//
114// It is called explicitly at start-up rather than from an init function so that
115// "which languages does this editor know?" is answered by reading main, not by
116// working out which packages were imported.
117//
118// No shebang is claimed. MoonBit has no interpreter line: a file opening with
119// #! would lex as an attribute named ! and fail, so a file with no extension is
120// not MoonBit, and guessing otherwise would take a shell script away from the
121// scanner that can actually colour it.
122func Register() {
123 syntax.Register(syntax.Definition{
124 Language: Language,
125 // .mbt is source; .mbti is a generated interface file, which is
126 // MoonBit and nothing else; .mbtx is a standalone script, which needs
127 // no module or package file at all.
128 //
129 // .mbt.md is deliberately not here, and could not be: it is a Markdown
130 // document with MoonBit in its fences, its extension is .md, and
131 // Markdown is what should colour it.
132 Extensions: []string{".mbt", ".mbti", ".mbtx"},
133 Highlight: Highlight,
134 })
135}
136
137// ServerDirs returns the directories moon-lsp is looked for in after PATH.
138//
139// "Completion silently does nothing" is what a user sees when the editor cannot
140// find a server they believe they installed, and the MoonBit installer's own
141// last act is to append its bin directory to a shell profile — which does
142// nothing for an editor started from a shell that was already open, or from a
143// desktop launcher that reads no profile at all.
144//
145// Empty entries are skipped by the library, so a machine with MOON_HOME unset
146// simply contributes the default alone.
147func ServerDirs() []string {
148 dirs := []string{MoonHomeBinDir(), DefaultMoonBinDir()}
149 if dirs[0] == dirs[1] {
150 return dirs[:1]
151 }
152 return dirs
153}
154
155// MoonHomeBinDir returns $MOON_HOME/bin, or "" when MOON_HOME is not set.
156//
157// MOON_HOME is what the installer itself honours when deciding where to put the
158// toolchain, so a user who set it has the whole toolchain somewhere this is the
159// only way to find.
160func MoonHomeBinDir() string {
161 home := os.Getenv("MOON_HOME")
162 if home == "" {
163 return ""
164 }
165 return filepath.Join(home, "bin")
166}
167
168// DefaultMoonBinDir returns ~/.moon/bin, where the installer puts the toolchain
169// when MOON_HOME says nothing.
170//
171// It is the directory the install hint's command writes into, so it is the one
172// that matters most to somebody who followed the hint and found nothing.
173func DefaultMoonBinDir() string {
174 home, err := os.UserHomeDir()
175 if err != nil {
176 return ""
177 }
178 return filepath.Join(home, ".moon", "bin")
179}